Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
26 changes: 26 additions & 0 deletions errs/errs.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,29 @@ func FileError(err error, filename string) error {
type FriendlyError interface {
Message() string
}

// RequiredInputError is returned when a prompt is required but no terminal is
// available. It provides a helpful error message indicating which flag can be
// used to provide the value non-interactively.
type RequiredInputError struct {
// FieldName is the name of the field that requires input (e.g., "password", "provisioner")
FieldName string
// Flag is the optional flag name that can be used to provide the value non-interactively
Flag string
}

// Error implements the error interface.
func (e *RequiredInputError) Error() string {
if e.Flag != "" {
return fmt.Sprintf("cannot prompt for %s: no terminal available. Use '--%s' flag to provide this value non-interactively", e.FieldName, e.Flag)
}
return fmt.Sprintf("cannot prompt for %s: no terminal available", e.FieldName)
}

// NewRequiredInputError creates a new RequiredInputError with the given field name and flag.
func NewRequiredInputError(fieldName, flag string) *RequiredInputError {
return &RequiredInputError{
FieldName: fieldName,
Flag: flag,
}
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
go.step.sm/crypto v0.75.0
golang.org/x/net v0.49.0
golang.org/x/sys v0.40.0
golang.org/x/term v0.39.0
)

require (
Expand Down
23 changes: 21 additions & 2 deletions ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,27 @@ import (
"github.com/manifoldco/promptui"
"github.com/pkg/errors"
"go.step.sm/crypto/randutil"
"golang.org/x/term"
)

// CanPrompt returns true if the current environment supports interactive
// prompts. It checks if stdin is a terminal, or if /dev/tty is available.
// This function should be called before attempting to prompt for user input
// to provide better error messages in non-interactive environments like
// systemd services, GitHub Actions, or Docker containers.
func CanPrompt() bool {
// First check if stdin is a terminal
if term.IsTerminal(int(os.Stdin.Fd())) {
return true
}
// Fall back to checking if /dev/tty is available
if tty, err := os.Open("/dev/tty"); err == nil {
tty.Close()
return true
}
return false
}

// stderr implements an io.WriteCloser that skips the terminal bell character
// (ASCII code 7), and writes the rest to os.Stderr. It's used to replace
// readline.Stdout, that is the package used by promptui to display the prompts.
Expand Down Expand Up @@ -299,7 +318,7 @@ func preparePromptTerminal() (func(), error) {
if !readline.DefaultIsTerminal() {
tty, err := os.Open("/dev/tty")
if err != nil {
return nothing, errors.Wrap(err, "error allocating terminal")
return nothing, errors.New("cannot perform interactive prompts: no terminal available (running in non-interactive environment like systemd, CI, or Docker?)")
}
clean := func() {
tty.Close()
Expand Down Expand Up @@ -329,7 +348,7 @@ func prepareSelectTerminal() (func(), error) {
if !readline.DefaultIsTerminal() {
tty, err := os.Open("/dev/tty")
if err != nil {
return nothing, errors.Wrap(err, "error allocating terminal")
return nothing, errors.New("cannot perform interactive selection: no terminal available (running in non-interactive environment like systemd, CI, or Docker?)")
}
clean := func() {
tty.Close()
Expand Down
Loading