Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/compose/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func promptForInterpolatedVariables(ctx context.Context, dockerCli command.Cli,

// Prompt for confirmation
userInput := prompt.NewPrompt(dockerCli.In(), dockerCli.Out())
msg := "\nDo you want to proceed with these variables? [Y/n]: "
msg := "\nDo you want to proceed with these variables?"
confirmed, err := userInput.Confirm(msg, true)
if err != nil {
return err
Expand Down Expand Up @@ -286,7 +286,7 @@ func confirmRemoteIncludes(dockerCli command.Cli, options buildOptions, assumeYe
}
_, _ = fmt.Fprintln(dockerCli.Out(), "\nRemote includes could potentially be malicious. Make sure you trust the source.")

msg := "Do you want to continue? [y/N]: "
msg := "Do you want to continue?"
confirmed, err := prompt.NewPrompt(dockerCli.In(), dockerCli.Out()).Confirm(msg, false)
if err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions cmd/compose/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ func TestConfirmRemoteIncludes(t *testing.T) {
" - oci://registry.example.com/stack:latest\n" +
" - git://github.com/user/repo.git\n" +
"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
"Do you want to continue? [y/N]: ",
"Do you want to continue?",
},
{
name: "user rejects remote includes",
Expand All @@ -422,7 +422,7 @@ func TestConfirmRemoteIncludes(t *testing.T) {
wantOutput: "\nWarning: This Compose project includes files from remote sources:\n" +
" - oci://registry.example.com/stack:latest\n" +
"\nRemote includes could potentially be malicious. Make sure you trust the source.\n" +
"Do you want to continue? [y/N]: ",
"Do you want to continue?",
},
}

Expand Down
109 changes: 71 additions & 38 deletions cmd/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,74 +17,107 @@
package prompt

import (
"bufio"
"errors"
"fmt"
"io"
"strings"
"unicode"

"github.com/AlecAivazis/survey/v2"
"github.com/docker/cli/cli/streams"

"github.com/docker/compose/v5/pkg/utils"
)

//go:generate mockgen -destination=./prompt_mock.go -self_package "github.com/docker/compose/v5/pkg/prompt" -package=prompt . UI

var errInterrupt = errors.New("interrupt")

// UI - prompt user input
type UI interface {
Confirm(message string, defaultValue bool) (bool, error)
}

func NewPrompt(stdin *streams.In, stdout *streams.Out) UI {
if stdin.IsTerminal() {
return User{stdin: streamsFileReader{stdin}, stdout: streamsFileWriter{stdout}}
return User{stdin: stdin, reader: bufio.NewReader(stdin), stdout: stdout}
}
return Pipe{stdin: stdin, stdout: stdout}
}

// User - in a terminal
type User struct {
stdout streamsFileWriter
stdin streamsFileReader
}

// adapt streams.Out to terminal.FileWriter
type streamsFileWriter struct {
stream *streams.Out
}

func (s streamsFileWriter) Write(p []byte) (n int, err error) {
return s.stream.Write(p)
}

func (s streamsFileWriter) Fd() uintptr {
return s.stream.FD()
stdout io.Writer
stdin *streams.In
reader *bufio.Reader
}

// adapt streams.In to terminal.FileReader
type streamsFileReader struct {
stream *streams.In
}
// Confirm asks for yes or no input
func (u User) Confirm(message string, defaultValue bool) (bool, error) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "(y/N)" hint survey auto-appended is lost, and three call sites relied on it — the description's "current callers already include the confirmation hint" holds for the two prompts in cmd/compose/options.go only. These don't carry any hint in their message:

  • pkg/compose/publish.go — "Are you ok to publish these bind mount declarations?" and "…these sensitive data?" (via confirmOrCancel);
  • pkg/bridge/convert.go — "Output directory … will be permanently deleted. Continue?".

After this change those render as a bare question: the user no longer sees the expected input format nor the default — on destructive confirmations. Minimal fix that preserves every caller at once: have Confirm append " [y/N]: " / " [Y/n]: " (per defaultValue) when the message doesn't already end with a hint; alternatively, fix the three messages in this same PR.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you go with option 2 (fixing individual messages rather than the centralized approach), don't miss two more call sites through the same Confirm with the same bare-question issue:

  • pkg/compose/publish.go:619buildEnvPromptMessage: "...Are you ok to publish these env declarations?"
  • pkg/compose/publish.go:629buildConfigContentPromptMessage: "...Are you ok to publish these config contents?"

if err := u.stdin.SetRawTerminal(); err != nil {
return false, err
}
defer u.stdin.RestoreTerminal()

func (s streamsFileReader) Read(p []byte) (n int, err error) {
return s.stream.Read(p)
}
prompt := " [y/N]: "
if defaultValue {
prompt = " [Y/n]: "
}

func (s streamsFileReader) Fd() uintptr {
return s.stream.FD()
for {
_, _ = fmt.Fprint(u.stdout, message+prompt)

answer, err := readLine(u.reader, u.stdout)
if err != nil {
return false, err
}

switch strings.ToLower(strings.TrimSpace(answer)) {
case "":
return defaultValue, nil
case "y", "yes":
return true, nil
case "n", "no":
return false, nil
}
}
}

// Confirm asks for yes or no input
func (u User) Confirm(message string, defaultValue bool) (bool, error) {
qs := &survey.Confirm{
Message: message,
Default: defaultValue,
func readLine(in io.RuneReader, out io.Writer) (string, error) {
var line []rune

for {
ch, _, err := in.ReadRune()
if err != nil {
return "", err
}

switch ch {
case 3: // Ctrl+C
_, _ = fmt.Fprint(out, "\r\n")
return "", errInterrupt

case 4: // Ctrl+D
return "", io.EOF

case '\r', '\n':
_, _ = fmt.Fprint(out, "\r\n")
return string(line), nil

case 127: // Backspace
if len(line) > 0 {
line = line[:len(line)-1]
_, _ = fmt.Fprint(out, "\b \b")
}

default:
if unicode.IsControl(ch) {
continue
}
line = append(line, ch)
_, _ = fmt.Fprintf(out, "%c", ch)
}
}
var b bool
err := survey.AskOne(qs, &b, func(options *survey.AskOptions) error {
options.Stdio.In = u.stdin
options.Stdio.Out = u.stdout
return nil
})
return b, err
}

// Pipe - aggregates prompt methods
Expand Down
Loading
Loading