Skip to content

Commit 10162cb

Browse files
fix: properly handle Ctrl+C and Ctrl+D in interactive prompts
- Add TTY check to prevent prompting in non-interactive contexts - Fix zone selection to properly propagate ErrInterrupt and ErrEOF - Add interrupt handling for zone selection in config add - Add test for Ctrl+C cancellation (exits 130, shows error) - Update test for Ctrl+D cancellation (exits 0, graceful) - All 18 E2E scenarios now passing
1 parent 9900e29 commit 10162cb

4 files changed

Lines changed: 50 additions & 8 deletions

File tree

cmd/config/config.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@ package config
22

33
import (
44
"fmt"
5-
"io"
65
"log"
76
"os"
87
"path"
98
"strings"
109

1110
"github.com/manifoldco/promptui"
1211
"github.com/spf13/cobra"
12+
"golang.org/x/term"
1313

1414
exocmd "github.com/exoscale/cli/cmd"
1515
"github.com/exoscale/cli/pkg/account"
@@ -33,6 +33,12 @@ func configCmdRun(cmd *cobra.Command, _ []string) error {
3333
log.Fatalf("remove ENV credentials variables to use %s", cmd.CalledAs())
3434
}
3535

36+
// Check if running non-interactively (no TTY)
37+
if !isTerminal(os.Stdin.Fd()) {
38+
printNoConfigMessage()
39+
return fmt.Errorf("interactive terminal required for config setup")
40+
}
41+
3642
if exocmd.GConfigFilePath != "" && account.CurrentAccount.Key != "" {
3743
accounts := listAccounts(defaultAccountMark)
3844
accounts = append(accounts, newAccountLabel)
@@ -246,15 +252,24 @@ func chooseZone(client *v3.Client, zones []string) (string, error) {
246252

247253
_, result, err := prompt.Run()
248254
if err != nil {
249-
if err == promptui.ErrInterrupt {
250-
return "", io.EOF // Return io.EOF to signal cancellation
255+
switch err {
256+
case promptui.ErrInterrupt:
257+
return "", promptui.ErrInterrupt // Propagate Ctrl+C
258+
case promptui.ErrEOF:
259+
return "", promptui.ErrEOF // Propagate Ctrl+D
260+
default:
261+
return "", fmt.Errorf("prompt failed: %w", err)
251262
}
252-
return "", fmt.Errorf("prompt failed: %w", err)
253263
}
254264

255265
return result, nil
256266
}
257267

268+
// isTerminal checks if the given file descriptor is a terminal
269+
func isTerminal(fd uintptr) bool {
270+
return term.IsTerminal(int(fd))
271+
}
272+
258273
func printNoConfigMessage() {
259274
fmt.Print(`No Exoscale CLI configuration found
260275

cmd/config/config_add.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,28 @@ func promptAccountInformation() (*account.Account, error) {
230230
}
231231
account.DefaultZone, err = chooseZone(client, nil)
232232
if err != nil {
233+
// Handle prompt cancellation
234+
if err == promptui.ErrInterrupt {
235+
fmt.Fprintln(os.Stderr, "Error: Operation Cancelled")
236+
os.Exit(exocmd.ExitCodeInterrupt)
237+
}
238+
if err == promptui.ErrEOF {
239+
fmt.Fprintln(os.Stderr, "")
240+
os.Exit(0)
241+
}
242+
// API error - try with fallback zones
233243
for {
234244
defaultZone, err := chooseZone(globalstate.EgoscaleV3Client, utils.AllZones)
235245
if err != nil {
246+
// Handle prompt cancellation in fallback
247+
if err == promptui.ErrInterrupt {
248+
fmt.Fprintln(os.Stderr, "Error: Operation Cancelled")
249+
os.Exit(exocmd.ExitCodeInterrupt)
250+
}
251+
if err == promptui.ErrEOF {
252+
fmt.Fprintln(os.Stderr, "")
253+
os.Exit(0)
254+
}
236255
return nil, err
237256
}
238257
if defaultZone != "" {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Test cancelling with Ctrl+C (interrupt) during API key prompt
2+
# Ctrl+C should show error message and exit with code 130
3+
4+
# Attempt to add account and cancel with Ctrl+C
5+
! execpty --stdin=inputs exo config add
6+
stderr 'Error: Operation Cancelled'
7+
8+
-- inputs --
9+
@ctrl+c
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
# Test cancelling with Ctrl+D (EOF) during API key prompt
2-
# Ctrl+D sends EOF signal which should be handled like Ctrl+C
2+
# Ctrl+D should exit gracefully with code 0 (not an error like Ctrl+C)
33

4-
# Attempt to add account and cancel with Ctrl+D
5-
! execpty --stdin=inputs exo config add
6-
stderr 'Error: Operation Cancelled'
4+
# Attempt to add account and cancel with Ctrl+D - should exit gracefully
5+
execpty --stdin=inputs exo config add
76

87
-- inputs --
98
@ctrl+d

0 commit comments

Comments
 (0)