summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2022-01-09 09:52:43 +1100
committerJesse Duffield <jessedduffield@gmail.com>2022-01-09 14:09:53 +1100
commit267ecbe6948c644e8953dda9d1914836cc82d560 (patch)
tree3213f01ae861fa8e0b2f51f2023befc9b3317c4f
parentccf90466fa1d7ed32a7211115d46c59846dfd622 (diff)
refactor code for handling credential requests
-rw-r--r--pkg/commands/oscommands/exec_live.go101
-rw-r--r--pkg/commands/oscommands/exec_live_default.go29
-rw-r--r--pkg/commands/oscommands/exec_live_win.go48
3 files changed, 82 insertions, 96 deletions
diff --git a/pkg/commands/oscommands/exec_live.go b/pkg/commands/oscommands/exec_live.go
index efddf355e..597bf5ba3 100644
--- a/pkg/commands/oscommands/exec_live.go
+++ b/pkg/commands/oscommands/exec_live.go
@@ -4,7 +4,6 @@ import (
"bufio"
"bytes"
"io"
- "os/exec"
"regexp"
"strings"
"unicode/utf8"
@@ -21,50 +20,20 @@ const (
Passphrase
)
-// RunAndDetectCredentialRequest detect a username / password / passphrase question in a command
-// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
-// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
-func (self *cmdObjRunner) RunAndDetectCredentialRequest(cmdObj ICmdObj, promptUserForCredential func(CredentialType) string) error {
- ttyText := ""
- err := self.RunCommandWithOutputLive(cmdObj, func(word string) string {
- ttyText = ttyText + " " + word
-
- prompts := map[string]CredentialType{
- `.+'s password:`: Password,
- `Password\s*for\s*'.+':`: Password,
- `Username\s*for\s*'.+':`: Username,
- `Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase,
- }
-
- for pattern, askFor := range prompts {
- if match, _ := regexp.MatchString(pattern, ttyText); match {
- ttyText = ""
- return promptUserForCredential(askFor)
- }
- }
-
- return ""
- })
- return err
-}
-
type cmdHandler struct {
stdoutPipe io.Reader
stdinPipe io.Writer
close func() error
}
-// RunCommandWithOutputLiveAux runs a command and return every word that gets written in stdout
-// Output is a function that executes by every word that gets read by bufio
-// As return of output you need to give a string that will be written to stdin
-// NOTE: If the return data is empty it won't write anything to stdin
-func (self *cmdObjRunner) RunCommandWithOutputLiveAux(
+// RunAndDetectCredentialRequest detect a username / password / passphrase question in a command
+// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
+// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
+func (self *cmdObjRunner) RunAndDetectCredentialRequest(
cmdObj ICmdObj,
- // handleOutput takes a word from stdout and returns a string to be written to stdin.
- // See RunAndDetectCredentialRequest above for how this is used to check for a username/password request
- handleOutput func(string) string,
- startCmd func(cmd *exec.Cmd) (*cmdHandler, error),
+ promptUserForCredential func(CredentialType) string,
) error {
+ self.log.Warn("HERE")
cmdWriter := self.guiIO.newCmdWriterFn()
self.log.WithField("command", cmdObj.ToString()).Info("RunCommand")
if cmdObj.ShouldLog() {
@@ -75,7 +44,7 @@ func (self *cmdObjRunner) RunCommandWithOutputLiveAux(
var stderr bytes.Buffer
cmd.Stderr = io.MultiWriter(cmdWriter, &stderr)
- handler, err := startCmd(cmd)
+ handler, err := self.getCmdHandler(cmd)
if err != nil {
return err
}
@@ -89,16 +58,7 @@ func (self *cmdObjRunner) RunCommandWithOutputLiveAux(
tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
go utils.Safe(func() {
- scanner := bufio.NewScanner(tr)
- scanner.Split(scanWordsWithNewLines)
- for scanner.Scan() {
- text := scanner.Text()
- output := strings.Trim(text, " ")
- toInput := handleOutput(output)
- if toInput != "" {
- _, _ = handler.stdinPipe.Write([]byte(toInput))
- }
- }
+ self.processOutput(tr, handler.stdinPipe, promptUserForCredential)
})
err = cmd.Wait()
@@ -109,6 +69,51 @@ func (self *cmdObjRunner) RunCommandWithOutputLiveAux(
return nil
}
+func (self *cmdObjRunner) processOutput(reader io.Reader, writer io.Writer, promptUserForCredential func(CredentialType) string) {
+ checkForCredentialRequest := self.getCheckForCredentialRequestFunc()
+
+ scanner := bufio.NewScanner(reader)
+ scanner.Split(scanWordsWithNewLines)
+ for scanner.Scan() {
+ text := scanner.Text()
+ self.log.Info(text)
+ output := strings.Trim(text, " ")
+ askFor, ok := checkForCredentialRequest(output)
+ if ok {
+ toInput := promptUserForCredential(askFor)
+ // If the return data is empty we don't write anything to stdin
+ if toInput != "" {
+ _, _ = writer.Write([]byte(toInput))
+ }
+ }
+ }
+}
+
+// having a function that returns a function because we need to maintain some state inbetween calls hence the closure
+func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func(string) (CredentialType, bool) {
+ ttyText := ""
+ // this function takes each word of output from the command and builds up a string to see if we're being asked for a password
+ return func(word string) (CredentialType, bool) {
+ ttyText = ttyText + " " + word
+
+ prompts := map[string]CredentialType{
+ `.+'s password:`: Password,
+ `Password\s*for\s*'.+':`: Password,
+ `Username\s*for\s*'.+':`: Username,
+ `Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase,
+ }
+
+ for pattern, askFor := range prompts {
+ if match, _ := regexp.MatchString(pattern, ttyText); match {
+ ttyText = ""
+ return askFor, true
+ }
+ }
+
+ return 0, false
+ }
+}
+
// scanWordsWithNewLines is a copy of bufio.ScanWords but this also captures new lines
// For specific comments about this function take a look at: bufio.ScanWords
func scanWordsWithNewLines(data []byte, atEOF bool) (advance int, token []byte, err error) {
diff --git a/pkg/commands/oscommands/exec_live_default.go b/pkg/commands/oscommands/exec_live_default.go
index 5066653c1..567dcf970 100644
--- a/pkg/commands/oscommands/exec_live_default.go
+++ b/pkg/commands/oscommands/exec_live_default.go
@@ -11,24 +11,15 @@ import (
// we define this separately for windows and non-windows given that windows does
// not have great PTY support and we need a PTY to handle a credential request
-func (self *cmdObjRunner) RunCommandWithOutputLive(
- cmdObj ICmdObj,
- output func(string) string,
-) error {
- return self.RunCommandWithOutputLiveAux(
- cmdObj,
- output,
- func(cmd *exec.Cmd) (*cmdHandler, error) {
- ptmx, err := pty.Start(cmd)
- if err != nil {
- return nil, err
- }
+func (self *cmdObjRunner) getCmdHandler(cmd *exec.Cmd) (*cmdHandler, error) {
+ ptmx, err := pty.Start(cmd)
+ if err != nil {
+ return nil, err
+ }
- return &cmdHandler{
- stdoutPipe: ptmx,
- stdinPipe: ptmx,
- close: ptmx.Close,
- }, nil
- },
- )
+ return &cmdHandler{
+ stdoutPipe: ptmx,
+ stdinPipe: ptmx,
+ close: ptmx.Close,
+ }, nil
}
diff --git a/pkg/commands/oscommands/exec_live_win.go b/pkg/commands/oscommands/exec_live_win.go
index 899e7c7d7..9e3d1fd02 100644
--- a/pkg/commands/oscommands/exec_live_win.go
+++ b/pkg/commands/oscommands/exec_live_win.go
@@ -26,34 +26,24 @@ func (b *Buffer) Write(p []byte) (n int, err error) {
return b.b.Write(p)
}
-// RunCommandWithOutputLive runs a command live but because of windows compatibility this command can't be ran there
// TODO: Remove this hack and replace it with a proper way to run commands live on windows. We still have an issue where if a password is requested, the request for a password is written straight to stdout because we can't control the stdout of a subprocess of a subprocess. Keep an eye on https://github.com/creack/pty/pull/109
-func (self *cmdObjRunner) RunCommandWithOutputLive(
- cmdObj ICmdObj,
- output func(string) string,
-) error {
- return self.RunCommandWithOutputLiveAux(
- cmdObj,
- output,
- func(cmd *exec.Cmd) (*cmdHandler, error) {
- stdoutReader, stdoutWriter := io.Pipe()
- cmd.Stdout = stdoutWriter
-
- buf := &Buffer{}
- cmd.Stdin = buf
-
- if err := cmd.Start(); err != nil {
- return nil, err
- }
-
- // because we don't yet have windows support for a pty, we instead just
- // pass our standard stream handlers and because there's no pty to close
- // we pass a no-op function for that.
- return &cmdHandler{
- stdoutPipe: stdoutReader,
- stdinPipe: buf,
- close: func() error { return nil },
- }, nil
- },
- )
+func (self *cmdObjRunner) getCmdHandler(cmd *exec.Cmd) (*cmdHandler, error) {
+ stdoutReader, stdoutWriter := io.Pipe()
+ cmd.Stdout = stdoutWriter
+
+ buf := &Buffer{}
+ cmd.Stdin = buf
+
+ if err := cmd.Start(); err != nil {
+ return nil, err
+ }
+
+ // because we don't yet have windows support for a pty, we instead just
+ // pass our standard stream handlers and because there's no pty to close
+ // we pass a no-op function for that.
+ return &cmdHandler{
+ stdoutPipe: stdoutReader,
+ stdinPipe: buf,
+ close: func() error { return nil },
+ }, nil
}