From 7e358708036a317af849ea33bf18d1faaaac17a2 Mon Sep 17 00:00:00 2001 From: joerger Date: Thu, 21 Sep 2023 11:09:20 -0700 Subject: [PATCH 1/3] Move /lib/utils/prompt to /api/utils/prompt. --- api/utils/prompt/confirmation.go | 110 ++++++ .../utils/prompt/confirmation_test.go | 0 api/utils/prompt/context_reader.go | 350 ++++++++++++++++++ .../utils/prompt/context_reader_test.go | 0 api/utils/prompt/mock.go | 78 ++++ api/utils/prompt/stdin.go | 71 ++++ lib/utils/prompt/confirmation.go | 99 +---- lib/utils/prompt/context_reader.go | 337 +---------------- lib/utils/prompt/mock.go | 67 +--- lib/utils/prompt/stdin.go | 57 +-- 10 files changed, 649 insertions(+), 520 deletions(-) create mode 100644 api/utils/prompt/confirmation.go rename {lib => api}/utils/prompt/confirmation_test.go (100%) create mode 100644 api/utils/prompt/context_reader.go rename {lib => api}/utils/prompt/context_reader_test.go (100%) create mode 100644 api/utils/prompt/mock.go create mode 100644 api/utils/prompt/stdin.go diff --git a/api/utils/prompt/confirmation.go b/api/utils/prompt/confirmation.go new file mode 100644 index 0000000000000..5d3cb19739c03 --- /dev/null +++ b/api/utils/prompt/confirmation.go @@ -0,0 +1,110 @@ +/* +Copyright 2021 Gravitational, Inc. + +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 prompt implements CLI prompts to the user. +package prompt + +import ( + "context" + "fmt" + "io" + "strings" + + "github.com/gravitational/trace" +) + +// Reader is the interface for prompt readers. +type Reader interface { + // ReadContext reads from the underlying buffer, respecting context + // cancellation. + ReadContext(ctx context.Context) ([]byte, error) +} + +// SecureReader is the interface for password readers. +type SecureReader interface { + // ReadPassword reads from the underlying buffer, respecting context + // cancellation. + ReadPassword(ctx context.Context) ([]byte, error) +} + +// Confirmation prompts the user for a yes/no confirmation for question. +// The prompt is written to out and the answer is read from in. +// +// question should be a plain sentence without "[yes/no]"-type hints at the end. +// +// ctx can be canceled to abort the prompt. +func Confirmation(ctx context.Context, out io.Writer, in Reader, question string) (bool, error) { + fmt.Fprintf(out, "%s [y/N]: ", question) + answer, err := in.ReadContext(ctx) + if err != nil { + return false, trace.WrapWithMessage(err, "failed reading prompt response") + } + switch strings.ToLower(strings.TrimSpace(string(answer))) { + case "y", "yes": + return true, nil + default: + return false, nil + } +} + +// PickOne prompts the user to pick one of the provided string options. +// The prompt is written to out and the answer is read from in. +// +// question should be a plain sentence without the list of provided options. +// +// ctx can be canceled to abort the prompt. +func PickOne(ctx context.Context, out io.Writer, in Reader, question string, options []string) (string, error) { + fmt.Fprintf(out, "%s [%s]: ", question, strings.Join(options, ", ")) + answerOrig, err := in.ReadContext(ctx) + if err != nil { + return "", trace.Wrap(err, "failed reading prompt response") + } + answer := strings.ToLower(strings.TrimSpace(string(answerOrig))) + for _, opt := range options { + if strings.ToLower(opt) == answer { + return opt, nil + } + } + return "", trace.BadParameter( + "%q is not a valid option, please specify one of [%s]", strings.TrimSpace(string(answerOrig)), strings.Join(options, ", ")) +} + +// Input prompts the user for freeform text input. +// The prompt is written to out and the answer is read from in. +// +// ctx can be canceled to abort the prompt. +func Input(ctx context.Context, out io.Writer, in Reader, question string) (string, error) { + fmt.Fprintf(out, "%s: ", question) + answer, err := in.ReadContext(ctx) + if err != nil { + return "", trace.Wrap(err, "failed reading prompt response") + } + return strings.TrimSpace(string(answer)), nil +} + +// Password prompts the user for a password. The prompt is written to out and +// the answer is read from in. +// The in reader has to be a terminal. +func Password(ctx context.Context, out io.Writer, in SecureReader, question string) (string, error) { + if question != "" { + fmt.Fprintf(out, "%s:\n", question) + } + answer, err := in.ReadPassword(ctx) + if err != nil { + return "", trace.Wrap(err, "failed reading prompt response") + } + return string(answer), nil // passwords not trimmed +} diff --git a/lib/utils/prompt/confirmation_test.go b/api/utils/prompt/confirmation_test.go similarity index 100% rename from lib/utils/prompt/confirmation_test.go rename to api/utils/prompt/confirmation_test.go diff --git a/api/utils/prompt/context_reader.go b/api/utils/prompt/context_reader.go new file mode 100644 index 0000000000000..7c81e76aac1a2 --- /dev/null +++ b/api/utils/prompt/context_reader.go @@ -0,0 +1,350 @@ +/* +Copyright 2021 Gravitational, Inc. + +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 prompt + +import ( + "bufio" + "context" + "errors" + "io" + "os" + "os/signal" + "sync" + + "github.com/gravitational/trace" + log "github.com/sirupsen/logrus" + "golang.org/x/term" +) + +// ErrReaderClosed is returned from ContextReader.ReadContext after it is +// closed. +var ErrReaderClosed = errors.New("ContextReader has been closed") + +// ErrNotTerminal is returned by password reads attempted in non-terminal +// readers. +var ErrNotTerminal = errors.New("underlying reader is not a terminal") + +const bufferSize = 4096 + +type readOutcome struct { + value []byte + err error +} + +type readerState int + +const ( + readerStateIdle readerState = iota + readerStateClean + readerStatePassword + readerStateClosed +) + +// termI aggregates methods from golang.org/x/term for easy mocking. +type termI interface { + GetState(fd int) (*term.State, error) + IsTerminal(fd int) bool + ReadPassword(fd int) ([]byte, error) + Restore(fd int, oldState *term.State) error +} + +// gxTerm delegates method calls to golang.org/x/term methods. +type gxTerm struct{} + +func (gxTerm) GetState(fd int) (*term.State, error) { + return term.GetState(fd) +} + +func (gxTerm) IsTerminal(fd int) bool { + return term.IsTerminal(fd) +} + +func (gxTerm) ReadPassword(fd int) ([]byte, error) { + return term.ReadPassword(fd) +} + +func (gxTerm) Restore(fd int, oldState *term.State) error { + return term.Restore(fd, oldState) +} + +// ContextReader is a wrapper around an underlying io.Reader or terminal that +// allows reads to be abandoned. An abandoned read may be reclaimed by future +// callers. +// ContextReader instances are not safe for concurrent use, callers may block +// indefinitely and reads may be lost. +type ContextReader struct { + term termI + + // reader is used for clean reads. + reader io.Reader + // fd is used for password reads. + // Only present if the underlying reader is a terminal, otherwise set to -1. + fd int + + closed chan struct{} + reads chan readOutcome + + mu *sync.Mutex + cond *sync.Cond + previousTermState *term.State + state readerState +} + +// NewContextReader creates a new ContextReader wrapping rd. +// Callers should avoid reading from rd after the ContextReader is used, as +// abandoned calls may be in progress. It is safe to read from rd if one can +// guarantee that no calls where abandoned. +// Calling ContextReader.Close attempts to release resources, but note that +// ongoing reads cannot be interrupted. +func NewContextReader(rd io.Reader) *ContextReader { + term := gxTerm{} + + fd := -1 + if f, ok := rd.(*os.File); ok { + val := int(f.Fd()) + if term.IsTerminal(val) { + fd = val + } + } + + mu := &sync.Mutex{} + cond := sync.NewCond(mu) + cr := &ContextReader{ + term: term, + reader: bufio.NewReader(rd), + fd: fd, + closed: make(chan struct{}), + reads: make(chan readOutcome), // unbuffered + mu: mu, + cond: cond, + } + go cr.processReads() + return cr +} + +func (cr *ContextReader) processReads() { + defer close(cr.reads) + + for { + cr.mu.Lock() + for cr.state == readerStateIdle { + cr.cond.Wait() + } + // Stop the reading loop? Once closed, forever closed. + if cr.state == readerStateClosed { + cr.mu.Unlock() + return + } + // React to the state that took us out of idleness. + // We can't hold the lock during the entire read, so we obey the last state + // observed. + state := cr.state + cr.mu.Unlock() + + var value []byte + var err error + switch state { + case readerStateClean: + value = make([]byte, bufferSize) + var n int + n, err = cr.reader.Read(value) + value = value[:n] + case readerStatePassword: + value, err = cr.term.ReadPassword(cr.fd) + } + cr.mu.Lock() + cr.previousTermState = nil // A finalized read resets the terminal. + switch cr.state { + case readerStateClosed: // Don't transition from closed. + default: + cr.state = readerStateIdle + } + cr.mu.Unlock() + + select { + case <-cr.closed: + log.Warnf("ContextReader closed during ongoing read, dropping %v bytes", len(value)) + return + case cr.reads <- readOutcome{value: value, err: err}: + } + } +} + +// IsTerminal returns whether the given reader is a terminal. +func (cr *ContextReader) IsTerminal() bool { + return cr.term.IsTerminal(cr.fd) +} + +// handleInterrupt restores terminal state on interrupts. +// Called only on global ContextReaders, such as Stdin. +func (cr *ContextReader) handleInterrupt() { + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + defer signal.Stop(c) + + for { + select { + case sig := <-c: + log.Debugf("Captured signal %s, attempting to restore terminal state", sig) + cr.mu.Lock() + _ = cr.maybeRestoreTerm(iAmHoldingTheLock{}) + cr.mu.Unlock() + case <-cr.closed: + return + } + } +} + +// iAmHoldingTheLock exists only to draw attention to the need to hold the lock. +type iAmHoldingTheLock struct{} + +// maybeRestoreTerm attempts to restore terminal state. +// Lock must be held before calling. +func (cr *ContextReader) maybeRestoreTerm(_ iAmHoldingTheLock) error { + if cr.state == readerStatePassword && cr.previousTermState != nil { + err := cr.term.Restore(cr.fd, cr.previousTermState) + cr.previousTermState = nil + return trace.Wrap(err) + } + + return nil +} + +// ReadContext returns the next chunk of output from the reader. +// If ctx is canceled before the read completes, the current read is abandoned +// and may be reclaimed by future callers. +// It is not safe to read from the underlying reader after a read is abandoned, +// nor is it safe to concurrently call ReadContext. +func (cr *ContextReader) ReadContext(ctx context.Context) ([]byte, error) { + if err := cr.fireCleanRead(); err != nil { + return nil, trace.Wrap(err) + } + + return cr.waitForRead(ctx) +} + +func (cr *ContextReader) fireCleanRead() error { + cr.mu.Lock() + defer cr.mu.Unlock() + + // Atempt to restore terminal state, so we transition to a clean read. + if err := cr.maybeRestoreTerm(iAmHoldingTheLock{}); err != nil { + return trace.Wrap(err) + } + + switch cr.state { + case readerStateIdle: // OK, transition and broadcast. + cr.state = readerStateClean + cr.cond.Broadcast() + case readerStateClean: // OK, ongoing read. + case readerStatePassword: // OK, ongoing read. + case readerStateClosed: + return ErrReaderClosed + } + return nil +} + +func (cr *ContextReader) waitForRead(ctx context.Context) ([]byte, error) { + select { + case <-ctx.Done(): + return nil, trace.Wrap(ctx.Err()) + case <-cr.closed: + return nil, ErrReaderClosed + case read := <-cr.reads: + return read.value, read.err + } +} + +// ReadPassword reads a password from the underlying reader, provided that the +// reader is a terminal. +// It follows the semantics of ReadContext. +func (cr *ContextReader) ReadPassword(ctx context.Context) ([]byte, error) { + if cr.fd == -1 { + return nil, ErrNotTerminal + } + if err := cr.firePasswordRead(); err != nil { + return nil, trace.Wrap(err) + } + + return cr.waitForRead(ctx) +} + +func (cr *ContextReader) firePasswordRead() error { + cr.mu.Lock() + defer cr.mu.Unlock() + + switch cr.state { + case readerStateIdle: // OK, transition and broadcast. + // Save present terminal state, so it may be restored in case the read goes + // from password to clean. + state, err := cr.term.GetState(cr.fd) + if err != nil { + return trace.Wrap(err) + } + cr.previousTermState = state + cr.state = readerStatePassword + cr.cond.Broadcast() + case readerStateClean: // OK, ongoing clean read. + // TODO(codingllama): Transition the terminal to password read? + log.Warn("prompt: Clean read reused by password read") + case readerStatePassword: // OK, ongoing password read. + case readerStateClosed: + return ErrReaderClosed + } + return nil +} + +// Close closes the context reader, attempting to release resources and aborting +// ongoing and future ReadContext calls. +// Background reads that are already blocked cannot be interrupted, thus Close +// doesn't guarantee a release of all resources. +func (cr *ContextReader) Close() error { + cr.mu.Lock() + defer cr.mu.Unlock() + + switch cr.state { + case readerStateClosed: // OK, already closed. + default: + // Attempt to restore terminal state on close. + _ = cr.maybeRestoreTerm(iAmHoldingTheLock{}) + + cr.state = readerStateClosed + close(cr.closed) // interrupt blocked sends. + cr.cond.Broadcast() + } + + return nil +} + +// PasswordReader is a ContextReader that reads passwords from the underlying +// terminal. +type PasswordReader ContextReader + +// Password returns a PasswordReader from a ContextReader. +// The returned PasswordReader is only functional if the underlying reader is a +// terminal. +func (cr *ContextReader) Password() *PasswordReader { + return (*PasswordReader)(cr) +} + +// ReadContext reads a password from the underlying reader, provided that the +// reader is a terminal. It is equivalent to ContextReader.ReadPassword. +// It follows the semantics of ReadContext. +func (pr *PasswordReader) ReadContext(ctx context.Context) ([]byte, error) { + cr := (*ContextReader)(pr) + return cr.ReadPassword(ctx) +} diff --git a/lib/utils/prompt/context_reader_test.go b/api/utils/prompt/context_reader_test.go similarity index 100% rename from lib/utils/prompt/context_reader_test.go rename to api/utils/prompt/context_reader_test.go diff --git a/api/utils/prompt/mock.go b/api/utils/prompt/mock.go new file mode 100644 index 0000000000000..de707584dcad3 --- /dev/null +++ b/api/utils/prompt/mock.go @@ -0,0 +1,78 @@ +// Copyright 2022 Gravitational, Inc +// +// 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 prompt + +import ( + "context" + "errors" + "sync" +) + +type FakeReplyFunc func(context.Context) (string, error) + +type FakeReader struct { + mu sync.Mutex + replies []FakeReplyFunc +} + +// NewFakeReader returns a fake that can be used in place of a ContextReader. +// Call Add functions in the desired order to configure responses. Each call +// represents a read reply, in order. +func NewFakeReader() *FakeReader { + return &FakeReader{} +} + +func (r *FakeReader) IsTerminal() bool { + return true +} + +func (r *FakeReader) AddReply(fn FakeReplyFunc) *FakeReader { + r.mu.Lock() + defer r.mu.Unlock() + r.replies = append(r.replies, fn) + return r +} + +func (r *FakeReader) AddString(s string) *FakeReader { + return r.AddReply(func(context.Context) (string, error) { + return s, nil + }) +} + +func (r *FakeReader) AddError(err error) *FakeReader { + return r.AddReply(func(context.Context) (string, error) { + return "", err + }) +} + +func (r *FakeReader) ReadContext(ctx context.Context) ([]byte, error) { + r.mu.Lock() + if len(r.replies) == 0 { + r.mu.Unlock() + return nil, errors.New("no fake replies available") + } + + // Pop first reply. + fn := r.replies[0] + r.replies = r.replies[1:] + r.mu.Unlock() + + val, err := fn(ctx) + return []byte(val), err +} + +func (r *FakeReader) ReadPassword(ctx context.Context) ([]byte, error) { + return r.ReadContext(ctx) +} diff --git a/api/utils/prompt/stdin.go b/api/utils/prompt/stdin.go new file mode 100644 index 0000000000000..452dd15f9a0f5 --- /dev/null +++ b/api/utils/prompt/stdin.go @@ -0,0 +1,71 @@ +/* +Copyright 2021 Gravitational, Inc. + +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 prompt + +import ( + "os" + "sync" +) + +var ( + stdinMU sync.Mutex + stdin StdinReader +) + +// StdinReader contains ContextReader methods applicable to stdin. +type StdinReader interface { + IsTerminal() bool + Reader + SecureReader +} + +// Stdin returns a singleton ContextReader wrapped around os.Stdin. +// +// os.Stdin should not be used directly after the first call to this function +// to avoid losing data. +func Stdin() StdinReader { + stdinMU.Lock() + defer stdinMU.Unlock() + if stdin == nil { + cr := NewContextReader(os.Stdin) + go cr.handleInterrupt() + stdin = cr + } + return stdin +} + +// SetStdin allows callers to change the Stdin reader. +// Useful to replace Stdin for tests, but should be avoided in production code. +func SetStdin(rd StdinReader) { + stdinMU.Lock() + defer stdinMU.Unlock() + stdin = rd +} + +// NotifyExit notifies prompt singletons, such as Stdin, that the program is +// about to exit. This allows singletons to perform actions such as restoring +// terminal state. +// Once NotifyExit is called the singletons will be closed. +func NotifyExit() { + // Note: don't call methods such as Stdin() here, we don't want to + // inadvertently hijack the prompts on exit. + stdinMU.Lock() + if cr, ok := stdin.(*ContextReader); ok { + _ = cr.Close() + } + stdinMU.Unlock() +} diff --git a/lib/utils/prompt/confirmation.go b/lib/utils/prompt/confirmation.go index 5d3cb19739c03..b97eecff52437 100644 --- a/lib/utils/prompt/confirmation.go +++ b/lib/utils/prompt/confirmation.go @@ -18,93 +18,18 @@ limitations under the License. package prompt import ( - "context" - "fmt" - "io" - "strings" - - "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/utils/prompt" ) -// Reader is the interface for prompt readers. -type Reader interface { - // ReadContext reads from the underlying buffer, respecting context - // cancellation. - ReadContext(ctx context.Context) ([]byte, error) -} - -// SecureReader is the interface for password readers. -type SecureReader interface { - // ReadPassword reads from the underlying buffer, respecting context - // cancellation. - ReadPassword(ctx context.Context) ([]byte, error) -} - -// Confirmation prompts the user for a yes/no confirmation for question. -// The prompt is written to out and the answer is read from in. -// -// question should be a plain sentence without "[yes/no]"-type hints at the end. -// -// ctx can be canceled to abort the prompt. -func Confirmation(ctx context.Context, out io.Writer, in Reader, question string) (bool, error) { - fmt.Fprintf(out, "%s [y/N]: ", question) - answer, err := in.ReadContext(ctx) - if err != nil { - return false, trace.WrapWithMessage(err, "failed reading prompt response") - } - switch strings.ToLower(strings.TrimSpace(string(answer))) { - case "y", "yes": - return true, nil - default: - return false, nil - } -} - -// PickOne prompts the user to pick one of the provided string options. -// The prompt is written to out and the answer is read from in. -// -// question should be a plain sentence without the list of provided options. -// -// ctx can be canceled to abort the prompt. -func PickOne(ctx context.Context, out io.Writer, in Reader, question string, options []string) (string, error) { - fmt.Fprintf(out, "%s [%s]: ", question, strings.Join(options, ", ")) - answerOrig, err := in.ReadContext(ctx) - if err != nil { - return "", trace.Wrap(err, "failed reading prompt response") - } - answer := strings.ToLower(strings.TrimSpace(string(answerOrig))) - for _, opt := range options { - if strings.ToLower(opt) == answer { - return opt, nil - } - } - return "", trace.BadParameter( - "%q is not a valid option, please specify one of [%s]", strings.TrimSpace(string(answerOrig)), strings.Join(options, ", ")) -} - -// Input prompts the user for freeform text input. -// The prompt is written to out and the answer is read from in. -// -// ctx can be canceled to abort the prompt. -func Input(ctx context.Context, out io.Writer, in Reader, question string) (string, error) { - fmt.Fprintf(out, "%s: ", question) - answer, err := in.ReadContext(ctx) - if err != nil { - return "", trace.Wrap(err, "failed reading prompt response") - } - return strings.TrimSpace(string(answer)), nil -} +// TODO(Joerger): Deprecated in favor of `api/utils/prompt`, delete once references are replaced. +type ( + Reader = prompt.Reader + SecureReader = prompt.SecureReader +) -// Password prompts the user for a password. The prompt is written to out and -// the answer is read from in. -// The in reader has to be a terminal. -func Password(ctx context.Context, out io.Writer, in SecureReader, question string) (string, error) { - if question != "" { - fmt.Fprintf(out, "%s:\n", question) - } - answer, err := in.ReadPassword(ctx) - if err != nil { - return "", trace.Wrap(err, "failed reading prompt response") - } - return string(answer), nil // passwords not trimmed -} +var ( + Confirmation = prompt.Confirmation + PickOne = prompt.PickOne + Input = prompt.Input + Password = prompt.Password +) diff --git a/lib/utils/prompt/context_reader.go b/lib/utils/prompt/context_reader.go index 7c81e76aac1a2..b0d9fca29a4e5 100644 --- a/lib/utils/prompt/context_reader.go +++ b/lib/utils/prompt/context_reader.go @@ -17,334 +17,17 @@ limitations under the License. package prompt import ( - "bufio" - "context" - "errors" - "io" - "os" - "os/signal" - "sync" - - "github.com/gravitational/trace" - log "github.com/sirupsen/logrus" - "golang.org/x/term" + "github.com/gravitational/teleport/api/utils/prompt" ) -// ErrReaderClosed is returned from ContextReader.ReadContext after it is -// closed. -var ErrReaderClosed = errors.New("ContextReader has been closed") - -// ErrNotTerminal is returned by password reads attempted in non-terminal -// readers. -var ErrNotTerminal = errors.New("underlying reader is not a terminal") - -const bufferSize = 4096 - -type readOutcome struct { - value []byte - err error -} - -type readerState int - -const ( - readerStateIdle readerState = iota - readerStateClean - readerStatePassword - readerStateClosed +// TODO(Joerger): Deprecated in favor of `api/utils/prompt`, delete once references are replaced. +type ( + ContextReader = prompt.ContextReader + PasswordReader = prompt.PasswordReader ) -// termI aggregates methods from golang.org/x/term for easy mocking. -type termI interface { - GetState(fd int) (*term.State, error) - IsTerminal(fd int) bool - ReadPassword(fd int) ([]byte, error) - Restore(fd int, oldState *term.State) error -} - -// gxTerm delegates method calls to golang.org/x/term methods. -type gxTerm struct{} - -func (gxTerm) GetState(fd int) (*term.State, error) { - return term.GetState(fd) -} - -func (gxTerm) IsTerminal(fd int) bool { - return term.IsTerminal(fd) -} - -func (gxTerm) ReadPassword(fd int) ([]byte, error) { - return term.ReadPassword(fd) -} - -func (gxTerm) Restore(fd int, oldState *term.State) error { - return term.Restore(fd, oldState) -} - -// ContextReader is a wrapper around an underlying io.Reader or terminal that -// allows reads to be abandoned. An abandoned read may be reclaimed by future -// callers. -// ContextReader instances are not safe for concurrent use, callers may block -// indefinitely and reads may be lost. -type ContextReader struct { - term termI - - // reader is used for clean reads. - reader io.Reader - // fd is used for password reads. - // Only present if the underlying reader is a terminal, otherwise set to -1. - fd int - - closed chan struct{} - reads chan readOutcome - - mu *sync.Mutex - cond *sync.Cond - previousTermState *term.State - state readerState -} - -// NewContextReader creates a new ContextReader wrapping rd. -// Callers should avoid reading from rd after the ContextReader is used, as -// abandoned calls may be in progress. It is safe to read from rd if one can -// guarantee that no calls where abandoned. -// Calling ContextReader.Close attempts to release resources, but note that -// ongoing reads cannot be interrupted. -func NewContextReader(rd io.Reader) *ContextReader { - term := gxTerm{} - - fd := -1 - if f, ok := rd.(*os.File); ok { - val := int(f.Fd()) - if term.IsTerminal(val) { - fd = val - } - } - - mu := &sync.Mutex{} - cond := sync.NewCond(mu) - cr := &ContextReader{ - term: term, - reader: bufio.NewReader(rd), - fd: fd, - closed: make(chan struct{}), - reads: make(chan readOutcome), // unbuffered - mu: mu, - cond: cond, - } - go cr.processReads() - return cr -} - -func (cr *ContextReader) processReads() { - defer close(cr.reads) - - for { - cr.mu.Lock() - for cr.state == readerStateIdle { - cr.cond.Wait() - } - // Stop the reading loop? Once closed, forever closed. - if cr.state == readerStateClosed { - cr.mu.Unlock() - return - } - // React to the state that took us out of idleness. - // We can't hold the lock during the entire read, so we obey the last state - // observed. - state := cr.state - cr.mu.Unlock() - - var value []byte - var err error - switch state { - case readerStateClean: - value = make([]byte, bufferSize) - var n int - n, err = cr.reader.Read(value) - value = value[:n] - case readerStatePassword: - value, err = cr.term.ReadPassword(cr.fd) - } - cr.mu.Lock() - cr.previousTermState = nil // A finalized read resets the terminal. - switch cr.state { - case readerStateClosed: // Don't transition from closed. - default: - cr.state = readerStateIdle - } - cr.mu.Unlock() - - select { - case <-cr.closed: - log.Warnf("ContextReader closed during ongoing read, dropping %v bytes", len(value)) - return - case cr.reads <- readOutcome{value: value, err: err}: - } - } -} - -// IsTerminal returns whether the given reader is a terminal. -func (cr *ContextReader) IsTerminal() bool { - return cr.term.IsTerminal(cr.fd) -} - -// handleInterrupt restores terminal state on interrupts. -// Called only on global ContextReaders, such as Stdin. -func (cr *ContextReader) handleInterrupt() { - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - defer signal.Stop(c) - - for { - select { - case sig := <-c: - log.Debugf("Captured signal %s, attempting to restore terminal state", sig) - cr.mu.Lock() - _ = cr.maybeRestoreTerm(iAmHoldingTheLock{}) - cr.mu.Unlock() - case <-cr.closed: - return - } - } -} - -// iAmHoldingTheLock exists only to draw attention to the need to hold the lock. -type iAmHoldingTheLock struct{} - -// maybeRestoreTerm attempts to restore terminal state. -// Lock must be held before calling. -func (cr *ContextReader) maybeRestoreTerm(_ iAmHoldingTheLock) error { - if cr.state == readerStatePassword && cr.previousTermState != nil { - err := cr.term.Restore(cr.fd, cr.previousTermState) - cr.previousTermState = nil - return trace.Wrap(err) - } - - return nil -} - -// ReadContext returns the next chunk of output from the reader. -// If ctx is canceled before the read completes, the current read is abandoned -// and may be reclaimed by future callers. -// It is not safe to read from the underlying reader after a read is abandoned, -// nor is it safe to concurrently call ReadContext. -func (cr *ContextReader) ReadContext(ctx context.Context) ([]byte, error) { - if err := cr.fireCleanRead(); err != nil { - return nil, trace.Wrap(err) - } - - return cr.waitForRead(ctx) -} - -func (cr *ContextReader) fireCleanRead() error { - cr.mu.Lock() - defer cr.mu.Unlock() - - // Atempt to restore terminal state, so we transition to a clean read. - if err := cr.maybeRestoreTerm(iAmHoldingTheLock{}); err != nil { - return trace.Wrap(err) - } - - switch cr.state { - case readerStateIdle: // OK, transition and broadcast. - cr.state = readerStateClean - cr.cond.Broadcast() - case readerStateClean: // OK, ongoing read. - case readerStatePassword: // OK, ongoing read. - case readerStateClosed: - return ErrReaderClosed - } - return nil -} - -func (cr *ContextReader) waitForRead(ctx context.Context) ([]byte, error) { - select { - case <-ctx.Done(): - return nil, trace.Wrap(ctx.Err()) - case <-cr.closed: - return nil, ErrReaderClosed - case read := <-cr.reads: - return read.value, read.err - } -} - -// ReadPassword reads a password from the underlying reader, provided that the -// reader is a terminal. -// It follows the semantics of ReadContext. -func (cr *ContextReader) ReadPassword(ctx context.Context) ([]byte, error) { - if cr.fd == -1 { - return nil, ErrNotTerminal - } - if err := cr.firePasswordRead(); err != nil { - return nil, trace.Wrap(err) - } - - return cr.waitForRead(ctx) -} - -func (cr *ContextReader) firePasswordRead() error { - cr.mu.Lock() - defer cr.mu.Unlock() - - switch cr.state { - case readerStateIdle: // OK, transition and broadcast. - // Save present terminal state, so it may be restored in case the read goes - // from password to clean. - state, err := cr.term.GetState(cr.fd) - if err != nil { - return trace.Wrap(err) - } - cr.previousTermState = state - cr.state = readerStatePassword - cr.cond.Broadcast() - case readerStateClean: // OK, ongoing clean read. - // TODO(codingllama): Transition the terminal to password read? - log.Warn("prompt: Clean read reused by password read") - case readerStatePassword: // OK, ongoing password read. - case readerStateClosed: - return ErrReaderClosed - } - return nil -} - -// Close closes the context reader, attempting to release resources and aborting -// ongoing and future ReadContext calls. -// Background reads that are already blocked cannot be interrupted, thus Close -// doesn't guarantee a release of all resources. -func (cr *ContextReader) Close() error { - cr.mu.Lock() - defer cr.mu.Unlock() - - switch cr.state { - case readerStateClosed: // OK, already closed. - default: - // Attempt to restore terminal state on close. - _ = cr.maybeRestoreTerm(iAmHoldingTheLock{}) - - cr.state = readerStateClosed - close(cr.closed) // interrupt blocked sends. - cr.cond.Broadcast() - } - - return nil -} - -// PasswordReader is a ContextReader that reads passwords from the underlying -// terminal. -type PasswordReader ContextReader - -// Password returns a PasswordReader from a ContextReader. -// The returned PasswordReader is only functional if the underlying reader is a -// terminal. -func (cr *ContextReader) Password() *PasswordReader { - return (*PasswordReader)(cr) -} - -// ReadContext reads a password from the underlying reader, provided that the -// reader is a terminal. It is equivalent to ContextReader.ReadPassword. -// It follows the semantics of ReadContext. -func (pr *PasswordReader) ReadContext(ctx context.Context) ([]byte, error) { - cr := (*ContextReader)(pr) - return cr.ReadPassword(ctx) -} +var ( + ErrReaderClosed = prompt.ErrReaderClosed + ErrNotTerminal = prompt.ErrNotTerminal + NewContextReader = prompt.NewContextReader +) diff --git a/lib/utils/prompt/mock.go b/lib/utils/prompt/mock.go index de707584dcad3..5dd8670f10a3c 100644 --- a/lib/utils/prompt/mock.go +++ b/lib/utils/prompt/mock.go @@ -15,64 +15,15 @@ package prompt import ( - "context" - "errors" - "sync" + "github.com/gravitational/teleport/api/utils/prompt" ) -type FakeReplyFunc func(context.Context) (string, error) - -type FakeReader struct { - mu sync.Mutex - replies []FakeReplyFunc -} - -// NewFakeReader returns a fake that can be used in place of a ContextReader. -// Call Add functions in the desired order to configure responses. Each call -// represents a read reply, in order. -func NewFakeReader() *FakeReader { - return &FakeReader{} -} - -func (r *FakeReader) IsTerminal() bool { - return true -} - -func (r *FakeReader) AddReply(fn FakeReplyFunc) *FakeReader { - r.mu.Lock() - defer r.mu.Unlock() - r.replies = append(r.replies, fn) - return r -} - -func (r *FakeReader) AddString(s string) *FakeReader { - return r.AddReply(func(context.Context) (string, error) { - return s, nil - }) -} - -func (r *FakeReader) AddError(err error) *FakeReader { - return r.AddReply(func(context.Context) (string, error) { - return "", err - }) -} - -func (r *FakeReader) ReadContext(ctx context.Context) ([]byte, error) { - r.mu.Lock() - if len(r.replies) == 0 { - r.mu.Unlock() - return nil, errors.New("no fake replies available") - } - - // Pop first reply. - fn := r.replies[0] - r.replies = r.replies[1:] - r.mu.Unlock() - - val, err := fn(ctx) - return []byte(val), err -} +// TODO(Joerger): Deprecated in favor of `api/utils/prompt`, delete once references are replaced. +type ( + FakeReplyFunc = prompt.FakeReplyFunc + FakeReader = prompt.FakeReader +) -func (r *FakeReader) ReadPassword(ctx context.Context) ([]byte, error) { - return r.ReadContext(ctx) -} +var ( + NewFakeReader = prompt.NewFakeReader +) diff --git a/lib/utils/prompt/stdin.go b/lib/utils/prompt/stdin.go index 452dd15f9a0f5..a4b9acf53598e 100644 --- a/lib/utils/prompt/stdin.go +++ b/lib/utils/prompt/stdin.go @@ -17,55 +17,16 @@ limitations under the License. package prompt import ( - "os" - "sync" + "github.com/gravitational/teleport/api/utils/prompt" ) -var ( - stdinMU sync.Mutex - stdin StdinReader +// TODO(Joerger): Deprecated in favor of `api/utils/prompt`, delete once references are replaced. +type ( + StdinReader = prompt.StdinReader ) -// StdinReader contains ContextReader methods applicable to stdin. -type StdinReader interface { - IsTerminal() bool - Reader - SecureReader -} - -// Stdin returns a singleton ContextReader wrapped around os.Stdin. -// -// os.Stdin should not be used directly after the first call to this function -// to avoid losing data. -func Stdin() StdinReader { - stdinMU.Lock() - defer stdinMU.Unlock() - if stdin == nil { - cr := NewContextReader(os.Stdin) - go cr.handleInterrupt() - stdin = cr - } - return stdin -} - -// SetStdin allows callers to change the Stdin reader. -// Useful to replace Stdin for tests, but should be avoided in production code. -func SetStdin(rd StdinReader) { - stdinMU.Lock() - defer stdinMU.Unlock() - stdin = rd -} - -// NotifyExit notifies prompt singletons, such as Stdin, that the program is -// about to exit. This allows singletons to perform actions such as restoring -// terminal state. -// Once NotifyExit is called the singletons will be closed. -func NotifyExit() { - // Note: don't call methods such as Stdin() here, we don't want to - // inadvertently hijack the prompts on exit. - stdinMU.Lock() - if cr, ok := stdin.(*ContextReader); ok { - _ = cr.Close() - } - stdinMU.Unlock() -} +var ( + Stdin = prompt.Stdin + SetStdin = prompt.SetStdin + NotifyExit = prompt.NotifyExit +) From eb8d01c52bba876b259e2777751ba1548a4c2f60 Mon Sep 17 00:00:00 2001 From: joerger Date: Thu, 21 Sep 2023 11:14:05 -0700 Subject: [PATCH 2/3] Replace uses of lib/utils/prompt with api/utils/prompt and delete pacakge. --- examples/dynamoathenamigration/migration.go | 2 +- .../dynamoathenamigration/migration_test.go | 2 +- integration/integration_test.go | 2 +- lib/auth/webauthncli/prompt.go | 2 +- lib/auth/webauthncli/prompt_test.go | 2 +- lib/client/api.go | 2 +- lib/client/api_login_test.go | 2 +- lib/client/identityfile/identity.go | 2 +- lib/client/keyagent.go | 2 +- lib/client/mfa/prompt.go | 2 +- lib/client/mfa_test.go | 2 +- lib/utils/prompt/confirmation.go | 35 ------------------- lib/utils/prompt/context_reader.go | 33 ----------------- lib/utils/prompt/mock.go | 29 --------------- lib/utils/prompt/stdin.go | 32 ----------------- tool/teleport/common/configurator.go | 2 +- tool/tsh/common/mfa.go | 2 +- tool/tsh/common/tsh.go | 2 +- tool/tsh/common/tsh_test.go | 2 +- 19 files changed, 15 insertions(+), 144 deletions(-) delete mode 100644 lib/utils/prompt/confirmation.go delete mode 100644 lib/utils/prompt/context_reader.go delete mode 100644 lib/utils/prompt/mock.go delete mode 100644 lib/utils/prompt/stdin.go diff --git a/examples/dynamoathenamigration/migration.go b/examples/dynamoathenamigration/migration.go index 4a007441ff237..8450798bd82af 100644 --- a/examples/dynamoathenamigration/migration.go +++ b/examples/dynamoathenamigration/migration.go @@ -48,9 +48,9 @@ import ( "golang.org/x/sync/errgroup" apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/events" "github.com/gravitational/teleport/lib/events/athena" - "github.com/gravitational/teleport/lib/utils/prompt" ) type Config struct { diff --git a/examples/dynamoathenamigration/migration_test.go b/examples/dynamoathenamigration/migration_test.go index 408ff669d06c2..b4bf9538af087 100644 --- a/examples/dynamoathenamigration/migration_test.go +++ b/examples/dynamoathenamigration/migration_test.go @@ -38,8 +38,8 @@ import ( "github.com/stretchr/testify/require" apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/teleport/lib/utils/prompt" ) func TestMigrateProcessDataObjects(t *testing.T) { diff --git a/integration/integration_test.go b/integration/integration_test.go index be8449195d0a8..f33742a1ebdcc 100644 --- a/integration/integration_test.go +++ b/integration/integration_test.go @@ -71,6 +71,7 @@ import ( apievents "github.com/gravitational/teleport/api/types/events" apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/api/utils/keypaths" + "github.com/gravitational/teleport/api/utils/prompt" apisshutils "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/integration/helpers" "github.com/gravitational/teleport/lib" @@ -98,7 +99,6 @@ import ( "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/teleport/lib/utils/prompt" "github.com/gravitational/teleport/lib/web" ) diff --git a/lib/auth/webauthncli/prompt.go b/lib/auth/webauthncli/prompt.go index 32b7e0948376f..943df0efaa46f 100644 --- a/lib/auth/webauthncli/prompt.go +++ b/lib/auth/webauthncli/prompt.go @@ -24,8 +24,8 @@ import ( "github.com/gravitational/trace" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/auth/touchid" - "github.com/gravitational/teleport/lib/utils/prompt" ) // DefaultPrompt is a default implementation for LoginPrompt and diff --git a/lib/auth/webauthncli/prompt_test.go b/lib/auth/webauthncli/prompt_test.go index bfd25efa4bc2b..da392e7097bd5 100644 --- a/lib/auth/webauthncli/prompt_test.go +++ b/lib/auth/webauthncli/prompt_test.go @@ -23,9 +23,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/auth/touchid" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" - "github.com/gravitational/teleport/lib/utils/prompt" ) func TestDefaultPrompt_PromptCredential(t *testing.T) { diff --git a/lib/client/api.go b/lib/client/api.go index 748bd8e1ee093..24d8e9e0084fa 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -63,6 +63,7 @@ import ( apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/api/utils/grpc/interceptors" "github.com/gravitational/teleport/api/utils/keys" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/auth/touchid" @@ -87,7 +88,6 @@ import ( "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/agentconn" - "github.com/gravitational/teleport/lib/utils/prompt" "github.com/gravitational/teleport/lib/utils/proxy" ) diff --git a/lib/client/api_login_test.go b/lib/client/api_login_test.go index 93ebae71ee083..a420d3c9ed478 100644 --- a/lib/client/api_login_test.go +++ b/lib/client/api_login_test.go @@ -39,6 +39,7 @@ import ( "github.com/gravitational/teleport/api/constants" devicepb "github.com/gravitational/teleport/api/gen/proto/go/teleport/devicetrust/v1" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/mocku2f" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" @@ -51,7 +52,6 @@ import ( "github.com/gravitational/teleport/lib/service/servicecfg" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/teleport/lib/utils/prompt" ) func TestTeleportClient_Login_local(t *testing.T) { diff --git a/lib/client/identityfile/identity.go b/lib/client/identityfile/identity.go index bf70211306636..c54fdbee9f520 100644 --- a/lib/client/identityfile/identity.go +++ b/lib/client/identityfile/identity.go @@ -39,12 +39,12 @@ import ( "github.com/gravitational/teleport/api/profile" "github.com/gravitational/teleport/api/utils/keypaths" "github.com/gravitational/teleport/api/utils/keys" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/kube/kubeconfig" "github.com/gravitational/teleport/lib/sshutils" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/teleport/lib/utils/prompt" ) // Format describes possible file formats how a user identity can be stored. diff --git a/lib/client/keyagent.go b/lib/client/keyagent.go index 39892df9d05a9..678cdf4677de2 100644 --- a/lib/client/keyagent.go +++ b/lib/client/keyagent.go @@ -34,10 +34,10 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/constants" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/api/utils/sshutils" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/tlsca" - "github.com/gravitational/teleport/lib/utils/prompt" ) // LocalKeyAgent holds Teleport certificates for a user connected to a cluster. diff --git a/lib/client/mfa/prompt.go b/lib/client/mfa/prompt.go index 3e3cf180a39c9..d270f296b7a8a 100644 --- a/lib/client/mfa/prompt.go +++ b/lib/client/mfa/prompt.go @@ -31,10 +31,10 @@ import ( "github.com/gravitational/teleport" "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/observability/tracing" + "github.com/gravitational/teleport/api/utils/prompt" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" "github.com/gravitational/teleport/lib/auth/webauthnwin" - "github.com/gravitational/teleport/lib/utils/prompt" ) // AdminMFAHintBeforePrompt is a hint used for MFA prompts for admin-level API requests. diff --git a/lib/client/mfa_test.go b/lib/client/mfa_test.go index a47812cd9e9b8..97819109ed04d 100644 --- a/lib/client/mfa_test.go +++ b/lib/client/mfa_test.go @@ -24,10 +24,10 @@ import ( "github.com/gravitational/teleport/api/client/proto" wanpb "github.com/gravitational/teleport/api/types/webauthn" + "github.com/gravitational/teleport/api/utils/prompt" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes" "github.com/gravitational/teleport/lib/client/mfa" - "github.com/gravitational/teleport/lib/utils/prompt" ) // TestPromptMFAChallenge_usingNonRegisteredDevice tests a specific MFA scenario diff --git a/lib/utils/prompt/confirmation.go b/lib/utils/prompt/confirmation.go deleted file mode 100644 index b97eecff52437..0000000000000 --- a/lib/utils/prompt/confirmation.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -Copyright 2021 Gravitational, Inc. - -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 prompt implements CLI prompts to the user. -package prompt - -import ( - "github.com/gravitational/teleport/api/utils/prompt" -) - -// TODO(Joerger): Deprecated in favor of `api/utils/prompt`, delete once references are replaced. -type ( - Reader = prompt.Reader - SecureReader = prompt.SecureReader -) - -var ( - Confirmation = prompt.Confirmation - PickOne = prompt.PickOne - Input = prompt.Input - Password = prompt.Password -) diff --git a/lib/utils/prompt/context_reader.go b/lib/utils/prompt/context_reader.go deleted file mode 100644 index b0d9fca29a4e5..0000000000000 --- a/lib/utils/prompt/context_reader.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -Copyright 2021 Gravitational, Inc. - -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 prompt - -import ( - "github.com/gravitational/teleport/api/utils/prompt" -) - -// TODO(Joerger): Deprecated in favor of `api/utils/prompt`, delete once references are replaced. -type ( - ContextReader = prompt.ContextReader - PasswordReader = prompt.PasswordReader -) - -var ( - ErrReaderClosed = prompt.ErrReaderClosed - ErrNotTerminal = prompt.ErrNotTerminal - NewContextReader = prompt.NewContextReader -) diff --git a/lib/utils/prompt/mock.go b/lib/utils/prompt/mock.go deleted file mode 100644 index 5dd8670f10a3c..0000000000000 --- a/lib/utils/prompt/mock.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2022 Gravitational, Inc -// -// 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 prompt - -import ( - "github.com/gravitational/teleport/api/utils/prompt" -) - -// TODO(Joerger): Deprecated in favor of `api/utils/prompt`, delete once references are replaced. -type ( - FakeReplyFunc = prompt.FakeReplyFunc - FakeReader = prompt.FakeReader -) - -var ( - NewFakeReader = prompt.NewFakeReader -) diff --git a/lib/utils/prompt/stdin.go b/lib/utils/prompt/stdin.go deleted file mode 100644 index a4b9acf53598e..0000000000000 --- a/lib/utils/prompt/stdin.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright 2021 Gravitational, Inc. - -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 prompt - -import ( - "github.com/gravitational/teleport/api/utils/prompt" -) - -// TODO(Joerger): Deprecated in favor of `api/utils/prompt`, delete once references are replaced. -type ( - StdinReader = prompt.StdinReader -) - -var ( - Stdin = prompt.Stdin - SetStdin = prompt.SetStdin - NotifyExit = prompt.NotifyExit -) diff --git a/tool/teleport/common/configurator.go b/tool/teleport/common/configurator.go index bb80e021def7e..1cff4cf4ac830 100644 --- a/tool/teleport/common/configurator.go +++ b/tool/teleport/common/configurator.go @@ -25,12 +25,12 @@ import ( "golang.org/x/exp/slices" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/config" "github.com/gravitational/teleport/lib/configurators" awsconfigurators "github.com/gravitational/teleport/lib/configurators/aws" "github.com/gravitational/teleport/lib/configurators/configuratorbuilder" "github.com/gravitational/teleport/lib/service/servicecfg" - "github.com/gravitational/teleport/lib/utils/prompt" ) // awsDatabaseTypes list of databases supported on the configurator. diff --git a/tool/tsh/common/mfa.go b/tool/tsh/common/mfa.go index a1fd8ddf0616b..1b114f0b14c2b 100644 --- a/tool/tsh/common/mfa.go +++ b/tool/tsh/common/mfa.go @@ -35,6 +35,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/constants" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/asciitable" "github.com/gravitational/teleport/lib/auth/touchid" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" @@ -44,7 +45,6 @@ import ( "github.com/gravitational/teleport/lib/client/mfa" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/teleport/lib/utils/prompt" "github.com/alecthomas/kingpin/v2" "github.com/ghodss/yaml" diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 5de21c69d052d..7d00a2a633ce1 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -61,6 +61,7 @@ import ( apievents "github.com/gravitational/teleport/api/types/events" "github.com/gravitational/teleport/api/types/wrappers" "github.com/gravitational/teleport/api/utils/keys" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/lib/asciitable" "github.com/gravitational/teleport/lib/auth" wancli "github.com/gravitational/teleport/lib/auth/webauthncli" @@ -81,7 +82,6 @@ import ( "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/utils/mlock" - "github.com/gravitational/teleport/lib/utils/prompt" "github.com/gravitational/teleport/tool/common" ) diff --git a/tool/tsh/common/tsh_test.go b/tool/tsh/common/tsh_test.go index d30a4b8e54cf1..ea5310dae2651 100644 --- a/tool/tsh/common/tsh_test.go +++ b/tool/tsh/common/tsh_test.go @@ -59,6 +59,7 @@ import ( apiutils "github.com/gravitational/teleport/api/utils" "github.com/gravitational/teleport/api/utils/keypaths" "github.com/gravitational/teleport/api/utils/keys" + "github.com/gravitational/teleport/api/utils/prompt" "github.com/gravitational/teleport/integration/kube" "github.com/gravitational/teleport/lib" "github.com/gravitational/teleport/lib/auth" @@ -80,7 +81,6 @@ import ( "github.com/gravitational/teleport/lib/sshutils/x11" "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" - "github.com/gravitational/teleport/lib/utils/prompt" "github.com/gravitational/teleport/tool/common" ) From 0910a7b0031b5df421f0e553ac58b85172d00559 Mon Sep 17 00:00:00 2001 From: joerger Date: Mon, 25 Sep 2023 11:27:50 -0700 Subject: [PATCH 3/3] go mod tidy. --- api/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/go.mod b/api/go.mod index 5cde32540cf6d..f134c45a7f343 100644 --- a/api/go.mod +++ b/api/go.mod @@ -24,6 +24,7 @@ require ( golang.org/x/crypto v0.13.0 golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb golang.org/x/net v0.15.0 + golang.org/x/term v0.12.0 google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 google.golang.org/grpc v1.58.1 google.golang.org/protobuf v1.31.0 @@ -45,7 +46,6 @@ require ( github.com/russellhaering/goxmldsig v1.4.0 // indirect go.opentelemetry.io/otel/metric v1.18.0 // indirect golang.org/x/sys v0.12.0 // indirect - golang.org/x/term v0.12.0 // indirect golang.org/x/text v0.13.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect