From 03bc2798f79513a8734d02720d231da1f0ea01d7 Mon Sep 17 00:00:00 2001 From: Nicolas De Loof Date: Wed, 22 Oct 2025 14:26:46 +0200 Subject: [PATCH] introduce WithPrompt to configure compose backend to use a plugable UI component for user interaction Signed-off-by: Nicolas De Loof --- cmd/compose/alpha.go | 2 +- cmd/compose/attach.go | 4 +- cmd/compose/build.go | 4 +- cmd/compose/commit.go | 4 +- cmd/compose/completion.go | 2 +- cmd/compose/compose.go | 2 +- cmd/compose/cp.go | 4 +- cmd/compose/create.go | 4 +- cmd/compose/down.go | 4 +- cmd/compose/events.go | 4 +- cmd/compose/exec.go | 4 +- cmd/compose/export.go | 4 +- cmd/compose/generate.go | 4 +- cmd/compose/images.go | 4 +- cmd/compose/kill.go | 4 +- cmd/compose/list.go | 4 +- cmd/compose/logs.go | 4 +- cmd/compose/options.go | 2 +- cmd/compose/pause.go | 8 ++-- cmd/compose/port.go | 4 +- cmd/compose/ps.go | 4 +- cmd/compose/publish.go | 4 +- cmd/compose/pull.go | 4 +- cmd/compose/push.go | 4 +- cmd/compose/remove.go | 4 +- cmd/compose/restart.go | 4 +- cmd/compose/run.go | 4 +- cmd/compose/scale.go | 4 +- cmd/compose/start.go | 4 +- cmd/compose/stop.go | 4 +- cmd/compose/top.go | 4 +- cmd/compose/top_test.go | 2 +- cmd/compose/up.go | 4 +- cmd/compose/viz.go | 4 +- cmd/compose/volumes.go | 4 +- cmd/compose/wait.go | 4 +- cmd/compose/watch.go | 4 +- cmd/main.go | 13 +++--- {pkg => cmd}/prompt/prompt.go | 0 {pkg => cmd}/prompt/prompt_mock.go | 0 pkg/api/api.go | 5 ++- pkg/compose/compose.go | 26 ++++++++++-- pkg/compose/create.go | 3 +- pkg/compose/publish.go | 64 ++++++++++++++---------------- pkg/compose/remove.go | 3 +- pkg/e2e/publish_test.go | 32 ++++++++------- 46 files changed, 152 insertions(+), 136 deletions(-) rename {pkg => cmd}/prompt/prompt.go (100%) rename {pkg => cmd}/prompt/prompt_mock.go (100%) diff --git a/cmd/compose/alpha.go b/cmd/compose/alpha.go index 74636393e18..b6c17592ba7 100644 --- a/cmd/compose/alpha.go +++ b/cmd/compose/alpha.go @@ -21,7 +21,7 @@ import ( ) // alphaCommand groups all experimental subcommands -func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func alphaCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { cmd := &cobra.Command{ Short: "Experimental commands", Use: "alpha [COMMAND]", diff --git a/cmd/compose/attach.go b/cmd/compose/attach.go index a4504a0abad..7abffdb8905 100644 --- a/cmd/compose/attach.go +++ b/cmd/compose/attach.go @@ -35,7 +35,7 @@ type attachOpts struct { proxy bool } -func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := attachOpts{ composeOptions: &composeOptions{ ProjectOptions: p, @@ -63,7 +63,7 @@ func attachCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service return runCmd } -func runAttach(ctx context.Context, dockerCli command.Cli, backend api.Service, opts attachOpts) error { +func runAttach(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts attachOpts) error { projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/build.go b/cmd/compose/build.go index 041c7b42dfa..08b9deb408d 100644 --- a/cmd/compose/build.go +++ b/cmd/compose/build.go @@ -90,7 +90,7 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, }, nil } -func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := buildOptions{ ProjectOptions: p, } @@ -148,7 +148,7 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return cmd } -func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, opts buildOptions, services []string) error { +func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts buildOptions, services []string) error { opts.All = true // do not drop resources as build may involve some dependencies by additional_contexts project, _, err := opts.ToProject(ctx, dockerCli, nil, cli.WithResolvedPaths(true), cli.WithoutEnvironmentResolution) if err != nil { diff --git a/cmd/compose/commit.go b/cmd/compose/commit.go index 9b07981a054..628e196df24 100644 --- a/cmd/compose/commit.go +++ b/cmd/compose/commit.go @@ -39,7 +39,7 @@ type commitOptions struct { index int } -func commitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func commitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { options := commitOptions{ ProjectOptions: p, } @@ -73,7 +73,7 @@ func commitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service return cmd } -func runCommit(ctx context.Context, dockerCli command.Cli, backend api.Service, options commitOptions) error { +func runCommit(ctx context.Context, dockerCli command.Cli, backend api.Compose, options commitOptions) error { projectName, err := options.toProjectName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/completion.go b/cmd/compose/completion.go index dd23ed0747d..0fc35d2790b 100644 --- a/cmd/compose/completion.go +++ b/cmd/compose/completion.go @@ -52,7 +52,7 @@ func completeServiceNames(dockerCli command.Cli, p *ProjectOptions) validArgsFn } } -func completeProjectNames(backend api.Service) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { +func completeProjectNames(backend api.Compose) func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { list, err := backend.List(cmd.Context(), api.ListOptions{ All: true, diff --git a/cmd/compose/compose.go b/cmd/compose/compose.go index 2bba69ad976..e2df853eefa 100644 --- a/cmd/compose/compose.go +++ b/cmd/compose/compose.go @@ -416,7 +416,7 @@ func RunningAsStandalone() bool { } // RootCommand returns the compose command with its child commands -func RootCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { //nolint:gocyclo +func RootCommand(dockerCli command.Cli, backend api.Compose) *cobra.Command { //nolint:gocyclo // filter out useless commandConn.CloseWrite warning message that can occur // when using a remote context that is unreachable: "commandConn.CloseWrite: commandconn: failed to wait: signal: killed" // https://github.com/docker/cli/blob/e1f24d3c93df6752d3c27c8d61d18260f141310c/cli/connhelper/commandconn/commandconn.go#L203-L215 diff --git a/cmd/compose/cp.go b/cmd/compose/cp.go index bd6281fc830..e6e946b5843 100644 --- a/cmd/compose/cp.go +++ b/cmd/compose/cp.go @@ -38,7 +38,7 @@ type copyOptions struct { copyUIDGID bool } -func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := copyOptions{ ProjectOptions: p, } @@ -73,7 +73,7 @@ func copyCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return copyCmd } -func runCopy(ctx context.Context, dockerCli command.Cli, backend api.Service, opts copyOptions) error { +func runCopy(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts copyOptions) error { name, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/create.go b/cmd/compose/create.go index a96d63fe1e5..79d4d10cf48 100644 --- a/cmd/compose/create.go +++ b/cmd/compose/create.go @@ -51,7 +51,7 @@ type createOptions struct { AssumeYes bool } -func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := createOptions{} buildOpts := buildOptions{ ProjectOptions: p, @@ -95,7 +95,7 @@ func createCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service return cmd } -func runCreate(ctx context.Context, _ command.Cli, backend api.Service, createOpts createOptions, buildOpts buildOptions, project *types.Project, services []string) error { +func runCreate(ctx context.Context, _ command.Cli, backend api.Compose, createOpts createOptions, buildOpts buildOptions, project *types.Project, services []string) error { if err := createOpts.Apply(project); err != nil { return err } diff --git a/cmd/compose/down.go b/cmd/compose/down.go index d3080ed2df5..275e1949f96 100644 --- a/cmd/compose/down.go +++ b/cmd/compose/down.go @@ -40,7 +40,7 @@ type downOptions struct { images string } -func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := downOptions{ ProjectOptions: p, } @@ -77,7 +77,7 @@ func downCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return downCmd } -func runDown(ctx context.Context, dockerCli command.Cli, backend api.Service, opts downOptions, services []string) error { +func runDown(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts downOptions, services []string) error { project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err diff --git a/cmd/compose/events.go b/cmd/compose/events.go index 181048c40f5..8dc4c0637d7 100644 --- a/cmd/compose/events.go +++ b/cmd/compose/events.go @@ -34,7 +34,7 @@ type eventsOpts struct { until string } -func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := eventsOpts{ composeOptions: &composeOptions{ ProjectOptions: p, @@ -55,7 +55,7 @@ func eventsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service return cmd } -func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Service, opts eventsOpts, services []string) error { +func runEvents(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts eventsOpts, services []string) error { name, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/exec.go b/cmd/compose/exec.go index a978b65ff3f..e68cd0f1738 100644 --- a/cmd/compose/exec.go +++ b/cmd/compose/exec.go @@ -48,7 +48,7 @@ type execOpts struct { interactive bool } -func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := execOpts{ composeOptions: &composeOptions{ ProjectOptions: p, @@ -100,7 +100,7 @@ func execCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return runCmd } -func runExec(ctx context.Context, dockerCli command.Cli, backend api.Service, opts execOpts) error { +func runExec(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts execOpts) error { projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/export.go b/cmd/compose/export.go index 8ad08b7d2ae..a66fc2c11d2 100644 --- a/cmd/compose/export.go +++ b/cmd/compose/export.go @@ -33,7 +33,7 @@ type exportOptions struct { index int } -func exportCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func exportCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { options := exportOptions{ ProjectOptions: p, } @@ -58,7 +58,7 @@ func exportCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service return cmd } -func runExport(ctx context.Context, dockerCli command.Cli, backend api.Service, options exportOptions) error { +func runExport(ctx context.Context, dockerCli command.Cli, backend api.Compose, options exportOptions) error { projectName, err := options.toProjectName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/generate.go b/cmd/compose/generate.go index a0b32057d34..c1a087d506e 100644 --- a/cmd/compose/generate.go +++ b/cmd/compose/generate.go @@ -30,7 +30,7 @@ type generateOptions struct { Format string } -func generateCommand(p *ProjectOptions, backend api.Service) *cobra.Command { +func generateCommand(p *ProjectOptions, backend api.Compose) *cobra.Command { opts := generateOptions{ ProjectOptions: p, } @@ -52,7 +52,7 @@ func generateCommand(p *ProjectOptions, backend api.Service) *cobra.Command { return cmd } -func runGenerate(ctx context.Context, backend api.Service, opts generateOptions, containers []string) error { +func runGenerate(ctx context.Context, backend api.Compose, opts generateOptions, containers []string) error { _, _ = fmt.Fprintln(os.Stderr, "generate command is EXPERIMENTAL") if len(containers) == 0 { return fmt.Errorf("at least one container must be specified") diff --git a/cmd/compose/images.go b/cmd/compose/images.go index 4f305e49195..e4f27023b8a 100644 --- a/cmd/compose/images.go +++ b/cmd/compose/images.go @@ -41,7 +41,7 @@ type imageOptions struct { Format string } -func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := imageOptions{ ProjectOptions: p, } @@ -58,7 +58,7 @@ func imagesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service return imgCmd } -func runImages(ctx context.Context, dockerCli command.Cli, backend api.Service, opts imageOptions, services []string) error { +func runImages(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts imageOptions, services []string) error { projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/kill.go b/cmd/compose/kill.go index c0faa75c60e..f9d6218e1d2 100644 --- a/cmd/compose/kill.go +++ b/cmd/compose/kill.go @@ -33,7 +33,7 @@ type killOptions struct { signal string } -func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := killOptions{ ProjectOptions: p, } @@ -54,7 +54,7 @@ func killCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return cmd } -func runKill(ctx context.Context, dockerCli command.Cli, backend api.Service, opts killOptions, services []string) error { +func runKill(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts killOptions, services []string) error { project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err diff --git a/cmd/compose/list.go b/cmd/compose/list.go index 38fb2ce8c54..86d72a2af45 100644 --- a/cmd/compose/list.go +++ b/cmd/compose/list.go @@ -38,7 +38,7 @@ type lsOptions struct { Filter opts.FilterOpt } -func listCommand(dockerCli command.Cli, backend api.Service) *cobra.Command { +func listCommand(dockerCli command.Cli, backend api.Compose) *cobra.Command { lsOpts := lsOptions{Filter: opts.NewFilterOpt()} lsCmd := &cobra.Command{ Use: "ls [OPTIONS]", @@ -61,7 +61,7 @@ var acceptedListFilters = map[string]bool{ "name": true, } -func runList(ctx context.Context, dockerCli command.Cli, backend api.Service, lsOpts lsOptions) error { +func runList(ctx context.Context, dockerCli command.Cli, backend api.Compose, lsOpts lsOptions) error { filters := lsOpts.Filter.Value() err := filters.Validate(acceptedListFilters) if err != nil { diff --git a/cmd/compose/logs.go b/cmd/compose/logs.go index 7661b02aed6..01d9c4cb752 100644 --- a/cmd/compose/logs.go +++ b/cmd/compose/logs.go @@ -40,7 +40,7 @@ type logsOptions struct { timestamps bool } -func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := logsOptions{ ProjectOptions: p, } @@ -70,7 +70,7 @@ func logsCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return logsCmd } -func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Service, opts logsOptions, services []string) error { +func runLogs(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts logsOptions, services []string) error { project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err diff --git a/cmd/compose/options.go b/cmd/compose/options.go index 9e879fb0716..2c864c58a96 100644 --- a/cmd/compose/options.go +++ b/cmd/compose/options.go @@ -30,9 +30,9 @@ import ( "github.com/compose-spec/compose-go/v2/template" "github.com/compose-spec/compose-go/v2/types" "github.com/docker/cli/cli/command" + "github.com/docker/compose/v2/cmd/prompt" "github.com/docker/compose/v2/internal/tracing" ui "github.com/docker/compose/v2/pkg/progress" - "github.com/docker/compose/v2/pkg/prompt" ) func applyPlatforms(project *types.Project, buildForSinglePlatform bool) error { diff --git a/cmd/compose/pause.go b/cmd/compose/pause.go index 6f34577192e..fe9adcaf3d9 100644 --- a/cmd/compose/pause.go +++ b/cmd/compose/pause.go @@ -29,7 +29,7 @@ type pauseOptions struct { *ProjectOptions } -func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := pauseOptions{ ProjectOptions: p, } @@ -44,7 +44,7 @@ func pauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return cmd } -func runPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pauseOptions, services []string) error { +func runPause(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts pauseOptions, services []string) error { project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err @@ -60,7 +60,7 @@ type unpauseOptions struct { *ProjectOptions } -func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := unpauseOptions{ ProjectOptions: p, } @@ -75,7 +75,7 @@ func unpauseCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic return cmd } -func runUnPause(ctx context.Context, dockerCli command.Cli, backend api.Service, opts unpauseOptions, services []string) error { +func runUnPause(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts unpauseOptions, services []string) error { project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err diff --git a/cmd/compose/port.go b/cmd/compose/port.go index 9d49a226202..ff580e68355 100644 --- a/cmd/compose/port.go +++ b/cmd/compose/port.go @@ -35,7 +35,7 @@ type portOptions struct { index int } -func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := portOptions{ ProjectOptions: p, } @@ -62,7 +62,7 @@ func portCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return cmd } -func runPort(ctx context.Context, dockerCli command.Cli, backend api.Service, opts portOptions, service string) error { +func runPort(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts portOptions, service string) error { projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/ps.go b/cmd/compose/ps.go index f393b2c50fb..b7e36cf5176 100644 --- a/cmd/compose/ps.go +++ b/cmd/compose/ps.go @@ -64,7 +64,7 @@ func (p *psOptions) parseFilter() error { return nil } -func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := psOptions{ ProjectOptions: p, } @@ -91,7 +91,7 @@ func psCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *c return psCmd } -func runPs(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, opts psOptions) error { +func runPs(ctx context.Context, dockerCli command.Cli, backend api.Compose, services []string, opts psOptions) error { project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err diff --git a/cmd/compose/publish.go b/cmd/compose/publish.go index 174c8fa8695..95d0745895d 100644 --- a/cmd/compose/publish.go +++ b/cmd/compose/publish.go @@ -37,7 +37,7 @@ type publishOptions struct { app bool } -func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := publishOptions{ ProjectOptions: p, } @@ -67,7 +67,7 @@ func publishCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic return cmd } -func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Service, opts publishOptions, repository string) error { +func runPublish(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts publishOptions, repository string) error { project, metrics, err := opts.ToProject(ctx, dockerCli, nil) if err != nil { return err diff --git a/cmd/compose/pull.go b/cmd/compose/pull.go index 4951fa61c19..34ef5155c05 100644 --- a/cmd/compose/pull.go +++ b/cmd/compose/pull.go @@ -42,7 +42,7 @@ type pullOptions struct { policy string } -func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func pullCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := pullOptions{ ProjectOptions: p, } @@ -97,7 +97,7 @@ func (opts pullOptions) apply(project *types.Project, services []string) (*types return project, nil } -func runPull(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pullOptions, services []string) error { +func runPull(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts pullOptions, services []string) error { project, _, err := opts.ToProject(ctx, dockerCli, services, cli.WithoutEnvironmentResolution) if err != nil { return err diff --git a/cmd/compose/push.go b/cmd/compose/push.go index 177f9f2ec7c..8bd07c99853 100644 --- a/cmd/compose/push.go +++ b/cmd/compose/push.go @@ -34,7 +34,7 @@ type pushOptions struct { Quiet bool } -func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := pushOptions{ ProjectOptions: p, } @@ -53,7 +53,7 @@ func pushCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return pushCmd } -func runPush(ctx context.Context, dockerCli command.Cli, backend api.Service, opts pushOptions, services []string) error { +func runPush(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts pushOptions, services []string) error { project, _, err := opts.ToProject(ctx, dockerCli, services) if err != nil { return err diff --git a/cmd/compose/remove.go b/cmd/compose/remove.go index adcd3663c3b..ac32afbe29d 100644 --- a/cmd/compose/remove.go +++ b/cmd/compose/remove.go @@ -31,7 +31,7 @@ type removeOptions struct { volumes bool } -func removeCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func removeCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := removeOptions{ ProjectOptions: p, } @@ -59,7 +59,7 @@ Any data which is not in a volume will be lost.`, return cmd } -func runRemove(ctx context.Context, dockerCli command.Cli, backend api.Service, opts removeOptions, services []string) error { +func runRemove(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts removeOptions, services []string) error { project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err diff --git a/cmd/compose/restart.go b/cmd/compose/restart.go index 718e15196f0..eeda62a82f4 100644 --- a/cmd/compose/restart.go +++ b/cmd/compose/restart.go @@ -33,7 +33,7 @@ type restartOptions struct { noDeps bool } -func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := restartOptions{ ProjectOptions: p, } @@ -55,7 +55,7 @@ func restartCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic return restartCmd } -func runRestart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts restartOptions, services []string) error { +func runRestart(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts restartOptions, services []string) error { project, name, err := opts.projectOrName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/run.go b/cmd/compose/run.go index 2c6362ef183..19f0b86fa5c 100644 --- a/cmd/compose/run.go +++ b/cmd/compose/run.go @@ -142,7 +142,7 @@ func (options runOptions) getEnvironment(resolve func(string) (string, bool)) (t return environment, nil } -func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func runCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { options := runOptions{ composeOptions: &composeOptions{ ProjectOptions: p, @@ -266,7 +266,7 @@ func normalizeRunFlags(f *pflag.FlagSet, name string) pflag.NormalizedName { return pflag.NormalizedName(name) } -func runRun(ctx context.Context, backend api.Service, project *types.Project, options runOptions, createOpts createOptions, buildOpts buildOptions, dockerCli command.Cli) error { +func runRun(ctx context.Context, backend api.Compose, project *types.Project, options runOptions, createOpts createOptions, buildOpts buildOptions, dockerCli command.Cli) error { project, err := options.apply(project) if err != nil { return err diff --git a/cmd/compose/scale.go b/cmd/compose/scale.go index afa1f8cb232..404aa7ba9b3 100644 --- a/cmd/compose/scale.go +++ b/cmd/compose/scale.go @@ -35,7 +35,7 @@ type scaleOptions struct { noDeps bool } -func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := scaleOptions{ ProjectOptions: p, } @@ -58,7 +58,7 @@ func scaleCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return scaleCmd } -func runScale(ctx context.Context, dockerCli command.Cli, backend api.Service, opts scaleOptions, serviceReplicaTuples map[string]int) error { +func runScale(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts scaleOptions, serviceReplicaTuples map[string]int) error { services := slices.Sorted(maps.Keys(serviceReplicaTuples)) project, _, err := opts.ToProject(ctx, dockerCli, services) if err != nil { diff --git a/cmd/compose/start.go b/cmd/compose/start.go index 6bde4b104bf..990f6b3990e 100644 --- a/cmd/compose/start.go +++ b/cmd/compose/start.go @@ -28,7 +28,7 @@ type startOptions struct { *ProjectOptions } -func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := startOptions{ ProjectOptions: p, } @@ -43,7 +43,7 @@ func startCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return startCmd } -func runStart(ctx context.Context, dockerCli command.Cli, backend api.Service, opts startOptions, services []string) error { +func runStart(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts startOptions, services []string) error { project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err diff --git a/cmd/compose/stop.go b/cmd/compose/stop.go index d06cf6f2290..2d8174e5694 100644 --- a/cmd/compose/stop.go +++ b/cmd/compose/stop.go @@ -32,7 +32,7 @@ type stopOptions struct { timeout int } -func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := stopOptions{ ProjectOptions: p, } @@ -53,7 +53,7 @@ func stopCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return cmd } -func runStop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts stopOptions, services []string) error { +func runStop(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts stopOptions, services []string) error { project, name, err := opts.projectOrName(ctx, dockerCli, services...) if err != nil { return err diff --git a/cmd/compose/top.go b/cmd/compose/top.go index a4cbc0f403b..056f6ffad91 100644 --- a/cmd/compose/top.go +++ b/cmd/compose/top.go @@ -34,7 +34,7 @@ type topOptions struct { *ProjectOptions } -func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func topCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := topOptions{ ProjectOptions: p, } @@ -54,7 +54,7 @@ type ( topEntries map[string]string ) -func runTop(ctx context.Context, dockerCli command.Cli, backend api.Service, opts topOptions, services []string) error { +func runTop(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts topOptions, services []string) error { projectName, err := opts.toProjectName(ctx, dockerCli) if err != nil { return err diff --git a/cmd/compose/top_test.go b/cmd/compose/top_test.go index a2172a061cc..3567c51c729 100644 --- a/cmd/compose/top_test.go +++ b/cmd/compose/top_test.go @@ -202,7 +202,7 @@ var topTestCases = []struct { } // TestRunTopCore only tests the core functionality of runTop: formatting -// and printing of the output of (api.Service).Top(). +// and printing of the output of (api.Compose).Top(). func TestRunTopCore(t *testing.T) { t.Parallel() diff --git a/cmd/compose/up.go b/cmd/compose/up.go index 0c8066d05b2..d6836c70d1a 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -109,7 +109,7 @@ func (opts upOptions) OnExit() api.Cascade { } } -func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func upCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { up := upOptions{} create := createOptions{} build := buildOptions{ProjectOptions: p} @@ -228,7 +228,7 @@ func validateFlags(up *upOptions, create *createOptions) error { func runUp( ctx context.Context, dockerCli command.Cli, - backend api.Service, + backend api.Compose, createOptions createOptions, upOptions upOptions, buildOptions buildOptions, diff --git a/cmd/compose/viz.go b/cmd/compose/viz.go index d97504e3825..9334243ab0e 100644 --- a/cmd/compose/viz.go +++ b/cmd/compose/viz.go @@ -35,7 +35,7 @@ type vizOptions struct { indentationStr string } -func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := vizOptions{ ProjectOptions: p, } @@ -63,7 +63,7 @@ func vizCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) * return cmd } -func runViz(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *vizOptions) error { +func runViz(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts *vizOptions) error { _, _ = fmt.Fprintln(os.Stderr, "viz command is EXPERIMENTAL") project, _, err := opts.ToProject(ctx, dockerCli, nil) if err != nil { diff --git a/cmd/compose/volumes.go b/cmd/compose/volumes.go index 456f586fcd8..3a2fa4971cc 100644 --- a/cmd/compose/volumes.go +++ b/cmd/compose/volumes.go @@ -34,7 +34,7 @@ type volumesOptions struct { Format string } -func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { options := volumesOptions{ ProjectOptions: p, } @@ -54,7 +54,7 @@ func volumesCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Servic return cmd } -func runVol(ctx context.Context, dockerCli command.Cli, backend api.Service, services []string, options volumesOptions) error { +func runVol(ctx context.Context, dockerCli command.Cli, backend api.Compose, services []string, options volumesOptions) error { project, name, err := options.projectOrName(ctx, dockerCli, services...) if err != nil { return err diff --git a/cmd/compose/wait.go b/cmd/compose/wait.go index 105f878786d..b998c82db83 100644 --- a/cmd/compose/wait.go +++ b/cmd/compose/wait.go @@ -34,7 +34,7 @@ type waitOptions struct { downProject bool } -func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { opts := waitOptions{ ProjectOptions: p, } @@ -60,7 +60,7 @@ func waitCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return cmd } -func runWait(ctx context.Context, dockerCli command.Cli, backend api.Service, opts *waitOptions) (int64, error) { +func runWait(ctx context.Context, dockerCli command.Cli, backend api.Compose, opts *waitOptions) (int64, error) { _, name, err := opts.projectOrName(ctx, dockerCli) if err != nil { return 0, err diff --git a/cmd/compose/watch.go b/cmd/compose/watch.go index 9fe2293f426..21e6e17fe43 100644 --- a/cmd/compose/watch.go +++ b/cmd/compose/watch.go @@ -36,7 +36,7 @@ type watchOptions struct { noUp bool } -func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) *cobra.Command { +func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Compose) *cobra.Command { watchOpts := watchOptions{ ProjectOptions: p, } @@ -64,7 +64,7 @@ func watchCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service) return cmd } -func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Service, watchOpts watchOptions, buildOpts buildOptions, services []string) error { +func runWatch(ctx context.Context, dockerCli command.Cli, backend api.Compose, watchOpts watchOptions, buildOpts buildOptions, services []string) error { project, _, err := watchOpts.ToProject(ctx, dockerCli, services) if err != nil { return err diff --git a/cmd/main.go b/cmd/main.go index 46f0e9aa72e..0fda6baf4a2 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -24,6 +24,7 @@ import ( "github.com/docker/cli/cli-plugins/plugin" "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/cmd/cmdtrace" + "github.com/docker/compose/v2/cmd/prompt" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -35,16 +36,18 @@ import ( func pluginMain() { plugin.Run( - func(dockerCli command.Cli) *cobra.Command { - backend := compose.NewComposeService(dockerCli) - cmd := commands.RootCommand(dockerCli, backend) + func(cli command.Cli) *cobra.Command { + backend := compose.NewComposeService(cli, + compose.WithPrompt(prompt.NewPrompt(cli.In(), cli.Out()).Confirm), + ) + cmd := commands.RootCommand(cli, backend) originalPreRunE := cmd.PersistentPreRunE cmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { - // initialize the dockerCli instance + // initialize the cli instance if err := plugin.PersistentPreRunE(cmd, args); err != nil { return err } - if err := cmdtrace.Setup(cmd, dockerCli, os.Args[1:]); err != nil { + if err := cmdtrace.Setup(cmd, cli, os.Args[1:]); err != nil { logrus.Debugf("failed to enable tracing: %v", err) } diff --git a/pkg/prompt/prompt.go b/cmd/prompt/prompt.go similarity index 100% rename from pkg/prompt/prompt.go rename to cmd/prompt/prompt.go diff --git a/pkg/prompt/prompt_mock.go b/cmd/prompt/prompt_mock.go similarity index 100% rename from pkg/prompt/prompt_mock.go rename to cmd/prompt/prompt_mock.go diff --git a/pkg/api/api.go b/pkg/api/api.go index c82c8618a4f..3f13472d009 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -30,8 +30,9 @@ import ( "github.com/docker/docker/api/types/volume" ) -// Service manages a compose project -type Service interface { +// Compose is the API interface one can use to programmatically use docker/compose in a third-party software +// Use [compose.NewComposeService] to get an actual instance +type Compose interface { // Build executes the equivalent to a `compose build` Build(ctx context.Context, project *types.Project, options BuildOptions) error // Push executes the equivalent to a `compose push` diff --git a/pkg/compose/compose.go b/pkg/compose/compose.go index f9c3707d39f..52f2e1b509f 100644 --- a/pkg/compose/compose.go +++ b/pkg/compose/compose.go @@ -50,18 +50,36 @@ func init() { } } -// NewComposeService create a local implementation of the compose.Service API -func NewComposeService(dockerCli command.Cli) api.Service { - return &composeService{ +type Option func(service *composeService) + +// NewComposeService create a local implementation of the compose.Compose API +func NewComposeService(dockerCli command.Cli, options ...Option) api.Compose { + s := &composeService{ dockerCli: dockerCli, clock: clockwork.NewRealClock(), maxConcurrency: -1, dryRun: false, } + for _, option := range options { + option(s) + } + return s } +// WithPrompt configure a UI component for Compose service to interact with user and confirm actions +func WithPrompt(prompt Prompt) Option { + return func(s *composeService) { + s.prompt = prompt + } +} + +type Prompt func(message string, defaultValue bool) (bool, error) + type composeService struct { - dockerCli command.Cli + dockerCli command.Cli + // prompt is used to interact with user and confirm actions + prompt Prompt + clock clockwork.Clock maxConcurrency int dryRun bool diff --git a/pkg/compose/create.go b/pkg/compose/create.go index 5ff84f395b4..277350906aa 100644 --- a/pkg/compose/create.go +++ b/pkg/compose/create.go @@ -44,7 +44,6 @@ import ( "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/progress" - "github.com/docker/compose/v2/pkg/prompt" ) type createOptions struct { @@ -1566,7 +1565,7 @@ func (s *composeService) ensureVolume(ctx context.Context, name string, volume t confirm := assumeYes if !assumeYes { msg := fmt.Sprintf("Volume %q exists but doesn't match configuration in compose file. Recreate (data will be lost)?", volume.Name) - confirm, err = prompt.NewPrompt(s.stdin(), s.stdout()).Confirm(msg, false) + confirm, err = s.prompt(msg, false) if err != nil { return "", err } diff --git a/pkg/compose/publish.go b/pkg/compose/publish.go index a504dc15115..396ad0f5831 100644 --- a/pkg/compose/publish.go +++ b/pkg/compose/publish.go @@ -25,18 +25,17 @@ import ( "fmt" "io" "os" + "strings" "github.com/DefangLabs/secret-detector/pkg/scanner" "github.com/DefangLabs/secret-detector/pkg/secrets" "github.com/compose-spec/compose-go/v2/loader" "github.com/compose-spec/compose-go/v2/types" "github.com/distribution/reference" - "github.com/docker/cli/cli/command" "github.com/docker/compose/v2/internal/oci" "github.com/docker/compose/v2/pkg/api" "github.com/docker/compose/v2/pkg/compose/transform" "github.com/docker/compose/v2/pkg/progress" - "github.com/docker/compose/v2/pkg/prompt" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/specs-go" v1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -280,16 +279,20 @@ func (s *composeService) preChecks(project *types.Project, options api.PublishOp } bindMounts := s.checkForBindMount(project) if len(bindMounts) > 0 { - fmt.Println("you are about to publish bind mounts declaration within your OCI artifact.\n" + + b := strings.Builder{} + b.WriteString("you are about to publish bind mounts declaration within your OCI artifact.\n" + "only the bind mount declarations will be added to the OCI artifact (not content)\n" + - "please double check that you are not mounting potential user's sensitive directories or data") + "please double check that you are not mounting potential user's sensitive directories or data\n") for key, val := range bindMounts { - _, _ = fmt.Fprintln(s.dockerCli.Out(), key) + b.WriteString(key) for _, v := range val { - _, _ = fmt.Fprintf(s.dockerCli.Out(), "%s\n", v.String()) + b.WriteString(v.String()) + b.WriteRune('\n') } } - if ok, err := acceptPublishBindMountDeclarations(s.dockerCli); err != nil || !ok { + b.WriteString("Are you ok to publish these bind mount declarations?") + confirm, err := s.prompt(b.String(), false) + if err != nil || !confirm { return false, err } } @@ -298,13 +301,17 @@ func (s *composeService) preChecks(project *types.Project, options api.PublishOp return false, err } if len(detectedSecrets) > 0 { - fmt.Println("you are about to publish sensitive data within your OCI artifact.\n" + - "please double check that you are not leaking sensitive data") + b := strings.Builder{} + b.WriteString("you are about to publish sensitive data within your OCI artifact.\n" + + "please double check that you are not leaking sensitive data\n") for _, val := range detectedSecrets { - _, _ = fmt.Fprintln(s.dockerCli.Out(), val.Type) - _, _ = fmt.Fprintf(s.dockerCli.Out(), "%q: %s\n", val.Key, val.Value) + b.WriteString(val.Type) + b.WriteRune('\n') + b.WriteString(fmt.Sprintf("%q: %s\n", val.Key, val.Value)) } - if ok, err := acceptPublishSensitiveData(s.dockerCli); err != nil || !ok { + b.WriteString("Are you ok to publish these sensitive data?") + confirm, err := s.prompt(b.String(), false) + if err != nil || !confirm { return false, err } } @@ -313,15 +320,20 @@ func (s *composeService) preChecks(project *types.Project, options api.PublishOp return false, err } if len(envVariables) > 0 { - fmt.Println("you are about to publish environment variables within your OCI artifact.\n" + - "please double check that you are not leaking sensitive data") + b := strings.Builder{} + b.WriteString("you are about to publish environment variables within your OCI artifact.\n" + + "please double check that you are not leaking sensitive data\n") for key, val := range envVariables { - _, _ = fmt.Fprintln(s.dockerCli.Out(), "Service/Config ", key) + b.WriteString("Service/Config ") + b.WriteString(key) + b.WriteRune('\n') for k, v := range val { - _, _ = fmt.Fprintf(s.dockerCli.Out(), "%s=%v\n", k, *v) + b.WriteString(fmt.Sprintf("%s=%v\n", k, *v)) } } - if ok, err := acceptPublishEnvVariables(s.dockerCli); err != nil || !ok { + b.WriteString("Are you ok to publish these environment variables?") + confirm, err := s.prompt(b.String(), false) + if err != nil || !confirm { return false, err } } @@ -364,24 +376,6 @@ func (s *composeService) checkEnvironmentVariables(project *types.Project, optio return envVarList, nil } -func acceptPublishEnvVariables(cli command.Cli) (bool, error) { - msg := "Are you ok to publish these environment variables? [y/N]: " - confirm, err := prompt.NewPrompt(cli.In(), cli.Out()).Confirm(msg, false) - return confirm, err -} - -func acceptPublishSensitiveData(cli command.Cli) (bool, error) { - msg := "Are you ok to publish these sensitive data? [y/N]: " - confirm, err := prompt.NewPrompt(cli.In(), cli.Out()).Confirm(msg, false) - return confirm, err -} - -func acceptPublishBindMountDeclarations(cli command.Cli) (bool, error) { - msg := "Are you ok to publish these bind mount declarations? [y/N]: " - confirm, err := prompt.NewPrompt(cli.In(), cli.Out()).Confirm(msg, false) - return confirm, err -} - func envFileLayers(project *types.Project) []v1.Descriptor { var layers []v1.Descriptor for _, service := range project.Services { diff --git a/pkg/compose/remove.go b/pkg/compose/remove.go index e8515a79a77..64ce5c4cab0 100644 --- a/pkg/compose/remove.go +++ b/pkg/compose/remove.go @@ -26,7 +26,6 @@ import ( "golang.org/x/sync/errgroup" "github.com/docker/compose/v2/pkg/progress" - "github.com/docker/compose/v2/pkg/prompt" ) func (s *composeService) Remove(ctx context.Context, projectName string, options api.RemoveOptions) error { @@ -85,7 +84,7 @@ func (s *composeService) Remove(ctx context.Context, projectName string, options if options.Force { _, _ = fmt.Fprintln(s.stdout(), msg) } else { - confirm, err := prompt.NewPrompt(s.stdin(), s.stdout()).Confirm(msg, false) + confirm, err := s.prompt(msg, false) if err != nil { return err } diff --git a/pkg/e2e/publish_test.go b/pkg/e2e/publish_test.go index 37d35df3655..5b768d45c9e 100644 --- a/pkg/e2e/publish_test.go +++ b/pkg/e2e/publish_test.go @@ -75,7 +75,7 @@ or remove sensitive data from your Compose configuration cmd.Stdin = strings.NewReader("y\n") res := icmd.RunCmd(cmd) res.Assert(t, icmd.Expected{ExitCode: 0}) - assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these environment variables? [y/N]:"), res.Combined()) + assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these environment variables?"), res.Combined()) assert.Assert(t, strings.Contains(res.Combined(), "test/test publishing"), res.Combined()) assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined()) }) @@ -86,7 +86,7 @@ or remove sensitive data from your Compose configuration cmd.Stdin = strings.NewReader("n\n") res := icmd.RunCmd(cmd) res.Assert(t, icmd.Expected{ExitCode: 0}) - assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these environment variables? [y/N]:"), res.Combined()) + assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these environment variables?"), res.Combined()) assert.Assert(t, !strings.Contains(res.Combined(), "test/test publishing"), res.Combined()) assert.Assert(t, !strings.Contains(res.Combined(), "test/test published"), res.Combined()) }) @@ -103,15 +103,16 @@ or remove sensitive data from your Compose configuration cmd.Stdin = strings.NewReader("n\n") res := icmd.RunCmd(cmd) res.Assert(t, icmd.Expected{ExitCode: 0}) - assert.Assert(t, strings.Contains(res.Combined(), `you are about to publish environment variables within your OCI artifact. -please double check that you are not leaking sensitive data`), res.Combined()) - assert.Assert(t, strings.Contains(res.Combined(), `Service/Config serviceA -FOO=bar`), res.Combined()) - assert.Assert(t, strings.Contains(res.Combined(), `Service/Config serviceB`), res.Combined()) + out := res.Combined() + assert.Assert(t, strings.Contains(out, `you are about to publish environment variables within your OCI artifact. +please double check that you are not leaking sensitive data`), out) + assert.Assert(t, strings.Contains(out, `Service/Config serviceA +FOO=bar`), out) + assert.Assert(t, strings.Contains(out, `Service/Config serviceB`), out) // we don't know in which order the env variables will be loaded - assert.Assert(t, strings.Contains(res.Combined(), `FOO=bar`), res.Combined()) - assert.Assert(t, strings.Contains(res.Combined(), `BAR=baz`), res.Combined()) - assert.Assert(t, strings.Contains(res.Combined(), `QUIX=`), res.Combined()) + assert.Assert(t, strings.Contains(out, `FOO=bar`), out) + assert.Assert(t, strings.Contains(out, `BAR=baz`), out) + assert.Assert(t, strings.Contains(out, `QUIX=`), out) }) t.Run("refuse to publish with bind mount", func(t *testing.T) { @@ -120,10 +121,11 @@ FOO=bar`), res.Combined()) cmd.Stdin = strings.NewReader("n\n") res := icmd.RunCmd(cmd) res.Assert(t, icmd.Expected{ExitCode: 0}) - assert.Assert(t, strings.Contains(res.Combined(), "you are about to publish bind mounts declaration within your OCI artifact."), res.Combined()) - assert.Assert(t, strings.Contains(res.Combined(), "e2e/fixtures/publish:/user-data"), res.Combined()) - assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these bind mount declarations? [y/N]:"), res.Combined()) - assert.Assert(t, !strings.Contains(res.Combined(), "serviceA published"), res.Combined()) + out := res.Combined() + assert.Assert(t, strings.Contains(out, "you are about to publish bind mounts declaration within your OCI artifact."), out) + assert.Assert(t, strings.Contains(out, "e2e/fixtures/publish:/user-data"), out) + assert.Assert(t, strings.Contains(out, "Are you ok to publish these bind mount declarations?"), out) + assert.Assert(t, !strings.Contains(out, "serviceA published"), out) }) t.Run("publish with bind mount", func(t *testing.T) { @@ -133,7 +135,7 @@ FOO=bar`), res.Combined()) res := icmd.RunCmd(cmd) res.Assert(t, icmd.Expected{ExitCode: 0}) assert.Assert(t, strings.Contains(res.Combined(), "you are about to publish bind mounts declaration within your OCI artifact."), res.Combined()) - assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these bind mount declarations? [y/N]:"), res.Combined()) + assert.Assert(t, strings.Contains(res.Combined(), "Are you ok to publish these bind mount declarations?"), res.Combined()) assert.Assert(t, strings.Contains(res.Combined(), "e2e/fixtures/publish:/user-data"), res.Combined()) assert.Assert(t, strings.Contains(res.Combined(), "test/test published"), res.Combined()) })