mirror of
https://github.com/docker/cli.git
synced 2026-09-27 09:46:01 -04:00
This was a remnant from when `PromptForConfirmation` accepted a `InStream`; it was added in [moby@30b8f08], which got left behind when [cli@37ccc00] updated its signature to accept a plain `io.Reader`. While updating, also update the comment to provide more context on the reason we're unconditionally using `os.Stdin` on Windows. [moby@30b8f08]: https://github.com/moby/moby/commit/30b8f084436a2a1d5e8523fcd2c5ea64cc805224 [cli@37ccc00]: https://github.com/docker/cli/commit/37ccc00d0e14461bcf29e98669773625dfed2fce Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
127 lines
3.4 KiB
Go
127 lines
3.4 KiB
Go
// Package prompt provides utilities to prompt the user for input.
|
|
|
|
package prompt
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"strings"
|
|
|
|
"github.com/docker/cli/cli/streams"
|
|
"github.com/moby/term"
|
|
)
|
|
|
|
const ErrTerminated cancelledErr = "prompt terminated"
|
|
|
|
type cancelledErr string
|
|
|
|
func (e cancelledErr) Error() string {
|
|
return string(e)
|
|
}
|
|
|
|
func (cancelledErr) Cancelled() {}
|
|
|
|
// DisableInputEcho disables input echo on the provided streams.In.
|
|
// This is useful when the user provides sensitive information like passwords.
|
|
// The function returns a restore function that should be called to restore the
|
|
// terminal state.
|
|
//
|
|
// TODO(thaJeztah): implement without depending on streams?
|
|
func DisableInputEcho(ins *streams.In) (restore func() error, _ error) {
|
|
oldState, err := term.SaveState(ins.FD())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
restore = func() error {
|
|
return term.RestoreTerminal(ins.FD(), oldState)
|
|
}
|
|
return restore, term.DisableEcho(ins.FD(), oldState)
|
|
}
|
|
|
|
// ReadInput requests input from the user.
|
|
//
|
|
// It returns an empty string ("") with an [ErrTerminated] if the user terminates
|
|
// the CLI with SIGINT or SIGTERM while the prompt is active. If the prompt
|
|
// returns an error, the caller should close the [io.Reader] used for the prompt
|
|
// and propagate the error up the stack to prevent the background goroutine
|
|
// from blocking indefinitely.
|
|
func ReadInput(ctx context.Context, in io.Reader, out io.Writer, message string) (string, error) {
|
|
_, _ = out.Write([]byte(message))
|
|
|
|
result := make(chan string)
|
|
go func() {
|
|
scanner := bufio.NewScanner(in)
|
|
if scanner.Scan() {
|
|
result <- strings.TrimSpace(scanner.Text())
|
|
}
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
_, _ = out.Write([]byte("\n"))
|
|
return "", ErrTerminated
|
|
case r := <-result:
|
|
return r, nil
|
|
}
|
|
}
|
|
|
|
// Confirm requests and checks confirmation from the user.
|
|
//
|
|
// It displays the provided message followed by "[y/N]". If the user
|
|
// input 'y' or 'Y' it returns true otherwise false. If no message is provided,
|
|
// "Are you sure you want to proceed? [y/N] " will be used instead.
|
|
//
|
|
// It returns false with an [ErrTerminated] if the user terminates
|
|
// the CLI with SIGINT or SIGTERM while the prompt is active. If the prompt
|
|
// returns an error, the caller should close the [io.Reader] used for the prompt
|
|
// and propagate the error up the stack to prevent the background goroutine
|
|
// from blocking indefinitely.
|
|
func Confirm(ctx context.Context, in io.Reader, out io.Writer, message string) (bool, error) {
|
|
if message == "" {
|
|
message = "Are you sure you want to proceed?"
|
|
}
|
|
message += " [y/N] "
|
|
|
|
_, _ = out.Write([]byte(message))
|
|
|
|
// On Windows, force the use of the regular OS stdin stream.
|
|
//
|
|
// StdStreams() may wrap stdin with windowsconsole.NewAnsiReader to
|
|
// emulate VT input on consoles that do not support it natively, but
|
|
// that wrapper has historically caused interactive prompts to hang
|
|
// or behave incorrectly.
|
|
//
|
|
// See:
|
|
// - https://github.com/moby/moby/issues/14336
|
|
// - https://github.com/moby/moby/issues/14210
|
|
// - https://github.com/moby/moby/pull/17738
|
|
if runtime.GOOS == "windows" {
|
|
in = os.Stdin
|
|
}
|
|
|
|
result := make(chan bool)
|
|
|
|
go func() {
|
|
var res bool
|
|
scanner := bufio.NewScanner(in)
|
|
if scanner.Scan() {
|
|
answer := strings.TrimSpace(scanner.Text())
|
|
if strings.EqualFold(answer, "y") {
|
|
res = true
|
|
}
|
|
}
|
|
result <- res
|
|
}()
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
_, _ = out.Write([]byte("\n"))
|
|
return false, ErrTerminated
|
|
case r := <-result:
|
|
return r, nil
|
|
}
|
|
}
|