Repository: bacalhau-project/bacalhau (language: python)

Issue:
CLI that accepts auth token does not respect Ctrl+C/D
In the event a user wants to abort inputting a token over the CLI to auth with a bacalhau node the command does not support canceling via Ctrl+c or Ctrl+d.


Discussion / hints:

Fix patch:
diff --git a/Makefile b/Makefile
index 036944ae47..1319ea7f8d 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,7 @@ endif
 export GO111MODULE = on
 export CGO_ENABLED = 0
 export PRECOMMIT = poetry run pre-commit
-export EARTHLY = $(shell which earthly)
+export EARTHLY ?= $(shell which earthly)
 
 BUILD_DIR = bacalhau
 BINARY_NAME = bacalhau
diff --git a/cmd/util/auth/ask.go b/cmd/util/auth/ask.go
index 82346766c6..b6d7ff351c 100644
--- a/cmd/util/auth/ask.go
+++ b/cmd/util/auth/ask.go
@@ -45,26 +45,44 @@ func askResponder(cmd *cobra.Command) responder {
 
 			fmt.Fprintf(cmd.ErrOrStderr(), "%s: ", name)
 
-			var input []byte
-			var err error
-
-			// If the property is marked as write only, assume it is a sensitive
-			// value and make sure we don't display it in the terminal
-			if subschema.WriteOnly && term.IsTerminal(int(os.Stdin.Fd())) {
-				input, err = term.ReadPassword(int(os.Stdin.Fd()))
-				fmt.Fprintln(cmd.ErrOrStderr())
-			} else {
-				reader := bufio.NewScanner(cmd.InOrStdin())
-				if reader.Scan() {
-					input = reader.Bytes()
+			inputChan := make(chan []byte, 1)
+			errChan := make(chan error, 1)
+
+			// Reading a value is not a cancellable operation. So we manually
+			// listen for the context here and write a final byte to the input
+			// if the user cancels, to hopefully get the input read to end.
+			go func(inputCh chan<- []byte, errCh chan<- error) {
+				var input []byte
+				var err error
+
+				// If the property is marked as write only, assume it is a sensitive
+				// value and make sure we don't display it in the terminal
+				if subschema.WriteOnly && term.IsTerminal(int(os.Stdin.Fd())) {
+					input, err = term.ReadPassword(int(os.Stdin.Fd()))
+				} else {
+					reader := bufio.NewScanner(cmd.InOrStdin())
+					if reader.Scan() {
+						input = reader.Bytes()
+					}
+					err = reader.Err()
 				}
-				err = reader.Err()
-			}
 
-			if err != nil {
+				if err != nil {
+					errCh <- err
+				} else {
+					inputCh <- input
+				}
+			}(inputChan, errChan)
+
+			select {
+			case input := <-inputChan:
+				response[name] = string(input)
+			case err = <-errChan:
 				return nil, err
+			case <-cmd.Context().Done():
+				_, _ = os.Stdin.Write([]byte{'\n'})
+				return nil, cmd.Context().Err()
 			}
-			response[name] = string(input)
 		}
 
 		respBytes, err := json.Marshal(response)


Test patch:
diff --git a/test/bin/bacalhau.sh b/test/bin/bacalhau.sh
index f7c26c2784..8af32d0426 100644
--- a/test/bin/bacalhau.sh
+++ b/test/bin/bacalhau.sh
@@ -1,8 +1,11 @@
 export IFS=$'\n\t'
 
+declare -a RUNNING_NODES
+
 new_repo() {
     export BACALHAU_DIR=$(mktemp -d)
     export BACALHAU_UPDATE_SKIPCHECKS=true
+    RUNNING_NODES=()
     bacalhau id >/dev/null 2>&1
 }
 
@@ -10,14 +13,12 @@ clean_repo() {
     rm -rf $BACALHAU_DIR
 }
 
-declare -i RUNNING_NODES=0
-
 create_node() {
     TYPE=$1
     shift 1
     $BACALHAU serve --peer=none --node-type=$TYPE $@ 1>$BACALHAU_DIR/out.log 2>$BACALHAU_DIR/err.log &
     NODE_PID=$!
-    RUNNING_NODES+=1
+    RUNNING_NODES+=($NODE_PID)
     {
         while ! ls $BACALHAU_DIR/bacalhau.run 2>/dev/null; do
             if ! ps $NODE_PID; then
@@ -25,6 +26,7 @@ create_node() {
                 echo `$BACALHAU serve --peer=none --node-type=$TYPE $@` 1>&2
                 exit 1
             fi
+            sleep 0.01
         done
     } 1>/dev/null
 
@@ -37,9 +39,10 @@ create_node() {
 }
 
 teardown_nodes() {
-    for i in $(seq 1 +1 $RUNNING_NODES 2>/dev/null || true); do
-        kill -15 %$i
-        wait %$i 2>/dev/null
+    for i in $RUNNING_NODES; do
+        while kill -15 $i 1>/dev/null 2>&1; do
+            sleep 0.01
+        done
     done;
 }
 
diff --git a/test/secret_auth.sh b/test/secret_auth.sh
index 744d36a321..0f8b9c0f72 100755
--- a/test/secret_auth.sh
+++ b/test/secret_auth.sh
@@ -33,6 +33,35 @@ testcase_token_works_for_v1_api() {
     assert_equal '[]' $stdout
 }
 
+testcase_input_can_be_cancelled() {
+    setup
+
+    # Run bacalhau job list in a screen session, else the auth code will detect
+    # that a TTY is not in use and won't use the hanging code
+    CMD='bacalhau job list --output=json'
+    rm -f screenlog.0
+    screen -L -d -m bacalhau job list --output=json
+    while ! grep 'token:' screenlog.0 1>/dev/null 2>&1; do sleep 0.01; done
+
+    PIDS=$(pgrep -fx $CMD)
+    assert_equal 0 $?
+    assert_not_equal '' $PIDS
+
+    # Send 'job list' an interrupt signal as if the user pressed Ctrl+C
+    kill -INT $PIDS
+    sleep 1
+
+    NOW=$(pgrep -fx $CMD)
+    assert_not_equal 0 $?
+    assert_equal '' $NOW
+
+    assert_not_match '[]' "$(cat screenlog.0)"
+    assert_match 'context canceled' "$(cat screenlog.0)"
+
+    rm screenlog.0
+    pkill -9 "$CMD"
+}
+
 testcase_invalid_token_is_rejected_and_not_persisted() {
     setup