package emulator import ( "errors" "fmt" "os" "os/exec" "strings" "github.com/c-bata/go-prompt" "edge-infra.dev/pkg/sds/lib/colors" ) func (em *Emulator) startShell(connectionDetails connectionData) error { // Setting up a session isn't required as part of local shell mode, however // making a connection can notify us of some potential issues that may occur // during remotecli exec, such as an invalid terminal ID, so we connect // here, and display any errors that might occur. If there are no errors // then we can immediately End the session and continue with shell mode. err := em.cls.Connect(em.runCtx, connectionDetails.projectID, connectionDetails.bannerID, connectionDetails.storeID, connectionDetails.terminalID) if err != nil { return err } err = em.cls.End() if err != nil { em.log.Error(err, "ending session") } fmt.Println(colors.Text("Shell prompt. 'Ctrl-D', 'end', 'exit' or 'q' to exit.", colors.BgGreen)) em.connectData = connectionDetails em.shellPrompt().Run() return nil } func (em *Emulator) shellPrompt() *prompt.Prompt { opts := []prompt.Option{ prompt.OptionSetExitCheckerOnInput(em.handleBreakLineExit), // History prompt.OptionHistory(em.shellHistory.history), // keybinding prompt.OptionAddKeyBind(commonKeyBindings...), // colors prompt.OptionPreviewSuggestionTextColor(prompt.Blue), prompt.OptionSelectedSuggestionBGColor(prompt.LightGray), prompt.OptionSuggestionBGColor(prompt.DarkGray), prompt.OptionPrefixTextColor(prompt.Yellow), prompt.OptionPrefix("<> "), } return prompt.New(em.shellPromptExecutor, shellPromptCompleter, opts..., ) } func (em *Emulator) shellPromptExecutor(in string) { if em.handleBreakLineExit(in, true) { return } in = strings.TrimRight(in, "\r") err := em.shellHistory.updateHistory(in, historyFileLimit) if err != nil { em.log.Error(err, "Error updating shell history file") } env := append(os.Environ(), em.cls.Env()..., ) env = append(env, envVar("RCLI_BANNER", em.connectData.bannerID), envVar("RCLI_STORE", em.connectData.storeID), envVar("RCLI_TERMINAL", em.connectData.terminalID), ) c := exec.Command("bash", "-c", in) if c.Err != nil { fmt.Printf("invalid command: %s\n", c.Err.Error()) return } c.Env = env out, rErr := c.CombinedOutput() var exitError *exec.ExitError if rErr != nil && !errors.As(rErr, &exitError) { // An ExitError indicates the process has started but exited with a non- // zero exit code. We want output in this case to be similar to a zero // exit code output so only need to handle a non-ExitError error here fmt.Println("Error occurred while executing command. See logs for details") em.log.Error(rErr, "error executing shell command") } fmt.Print(string(out)) exitCode := c.ProcessState.ExitCode() if exitCode == 0 { fmt.Println(colors.BufferedText("Exit code: %d", colors.BgGreen, exitCode)) } else { fmt.Println(colors.BufferedText("Exit code: %d", colors.BgRed, exitCode)) } } // creates a string which can be used as an env option within golang's os/exec // package when specifying env vars func envVar(name, value string) string { return name + "=" + value } func shellPromptCompleter(_ prompt.Document) []prompt.Suggest { return []prompt.Suggest{} }