run commands inside TUI and return to list

master
Joshua Herring 1 month ago
parent cb1ce4dab9
commit 3a9175e5d7

@ -2,15 +2,17 @@ package commands
import (
"fmt"
"io"
"os"
)
type RunCommandArgs struct {
Command string `json:"command"`
RemoteCommand string `json:"remote_command"`
Command string `json:"command"`
RemoteCommand string `json:"remote_command"`
PrivateKeyFile string `json:"private_key_file"`
Username string `json:"username"`
Host string `json:"host"`
Username string `json:"username"`
Host string `json:"host"`
Sudo bool `json:"sudo"`
}
func (rca *RunCommandArgs) ReadArgs() {
@ -28,27 +30,25 @@ func (rca *RunCommandArgs) ReadArgs() {
}
func RunCommand(args RunCommandArgs) {
func RunCommand(args RunCommandArgs, stdout, stderr io.Writer) error {
//args := RunCommandArgs{}
//args.ReadArgs()
client := GetClient(args.PrivateKeyFile, args.Username, args.Host)
client, err := GetClient(args.PrivateKeyFile, args.Username, args.Host)
if err != nil {
return err
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating SSH session: %v\n", err)
os.Exit(1)
return fmt.Errorf("error creating SSH session: %w", err)
}
defer session.Close()
session.Stdout = os.Stdout
session.Stderr = os.Stderr
session.Stdout = stdout
session.Stderr = stderr
if err := session.Run(args.RemoteCommand); err != nil {
fmt.Fprintf(os.Stderr, "Error running command: %v\n", err)
os.Exit(1)
return fmt.Errorf("error running command: %w", err)
}
return
return nil
}

@ -4,17 +4,16 @@ import (
"fmt"
"io"
"os"
"path/filepath"
)
type UploadFileArgs struct {
Command string `json:"command"`
LocalFilepath string `json:"local_filepath"`
Command string `json:"command"`
LocalFilepath string `json:"local_filepath"`
DestinationFilepath string `json:"destination_filepath"`
PrivateKeyFile string `json:"private_key_file"`
Username string `json:"username"`
Host string `json:"host"`
PrivateKeyFile string `json:"private_key_file"`
Username string `json:"username"`
Host string `json:"host"`
}
func (ufa *UploadFileArgs) ReadArgs() {
@ -33,58 +32,52 @@ func (ufa *UploadFileArgs) ReadArgs() {
}
func UploadFile(args UploadFileArgs) {
//args := UploadFileArgs{}
//args.ReadArgs()
func UploadFile(args UploadFileArgs, stdout, stderr io.Writer) error {
client := GetClient(args.PrivateKeyFile, args.Username, args.Host)
client, err := GetClient(args.PrivateKeyFile, args.Username, args.Host)
if err != nil {
return err
}
defer client.Close()
session, err := client.NewSession()
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating SSH session: %v\n", err)
os.Exit(1)
return fmt.Errorf("error creating SSH session: %w", err)
}
defer session.Close()
source_file, err := os.Open(args.LocalFilepath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error opening local file: %v\n", err)
os.Exit(1)
return fmt.Errorf("error opening local file: %w", err)
}
defer source_file.Close()
info, err := source_file.Stat()
if err != nil {
fmt.Fprintf(os.Stderr, "Error stating local file: %v\n", err)
os.Exit(1)
return fmt.Errorf("error stating local file: %w", err)
}
w, err := session.StdinPipe()
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating stdin pipe: %v\n", err)
os.Exit(1)
return fmt.Errorf("error creating stdin pipe: %w", err)
}
if err := session.Start("scp -t " + args.DestinationFilepath); err != nil {
fmt.Fprintf(os.Stderr, "Error starting scp: %v\n", err)
os.Exit(1)
return fmt.Errorf("error starting scp: %w", err)
}
fmt.Fprintf(w, "C0644 %d %s\n", info.Size(), filepath.Base(args.LocalFilepath))
if _, err := io.Copy(w, source_file); err != nil {
fmt.Fprintf(os.Stderr, "Error sending file: %v\n", err)
os.Exit(1)
return fmt.Errorf("error sending file: %w", err)
}
fmt.Fprint(w, "\x00")
w.Close()
if err := session.Wait(); err != nil {
fmt.Fprintf(os.Stderr, "Error during scp transfer: %v\n", err)
os.Exit(1)
return fmt.Errorf("error during scp transfer: %w", err)
}
fmt.Println("Success")
}
fmt.Fprintln(stdout, "Success")
return nil
}

@ -29,27 +29,28 @@ func ResolvePort(host string) string {
}
func GetSigner(private_key_file string) ssh.Signer {
func GetSigner(private_key_file string) (ssh.Signer, error) {
key_bytes, err := os.ReadFile(private_key_file)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading private key from file %s: %v\n", private_key_file, err)
os.Exit(1)
return nil, fmt.Errorf("error reading private key from file %s: %w", private_key_file, err)
}
signer, err := ssh.ParsePrivateKey(key_bytes)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing private key: %v\n", err)
os.Exit(1)
return nil, fmt.Errorf("error parsing private key: %w", err)
}
return signer
return signer, nil
}
func GetClient(private_key_file, username, host string) *ssh.Client {
func GetClient(private_key_file, username, host string) (*ssh.Client, error) {
signer := GetSigner(private_key_file)
signer, err := GetSigner(private_key_file)
if err != nil {
return nil, err
}
config := &ssh.ClientConfig{
User: username,
@ -63,10 +64,8 @@ func GetClient(private_key_file, username, host string) *ssh.Client {
client, err := ssh.Dial("tcp", addr, config)
if err != nil {
fmt.Fprintf(os.Stderr, "Error connecting to server: %v\n", err)
os.Exit(1)
return nil, fmt.Errorf("error connecting to server: %w", err)
}
return client
return client, nil
}

@ -1,8 +1,8 @@
package main
import (
"bytes"
"fmt"
"io"
"os"
"github.com/charmbracelet/bubbles/list"
@ -11,37 +11,34 @@ import (
"periodic/prddeploy/commands"
)
type sshExecCmd struct {
run func()
}
func (c *sshExecCmd) Run() error { c.run(); return nil }
func (c *sshExecCmd) SetStdin(_ io.Reader) {}
func (c *sshExecCmd) SetStdout(_ io.Writer) {}
func (c *sshExecCmd) SetStderr(_ io.Writer) {}
type appState int
const (
stateList appState = iota
stateForm
stateOutput
)
type cmdResultMsg struct {
output string
}
type CommandItem struct {
ItemTitle string `json:"title"`
ItemTitle string `json:"title"`
ItemDescription string `json:"description"`
}
func (i CommandItem) FilterValue() string { return i.ItemTitle }
func (i CommandItem) Title() string { return i.ItemTitle }
func (i CommandItem) Description() string { return i.ItemDescription }
func (i CommandItem) String() string { return i.ItemTitle }
func (i CommandItem) String() string { return i.ItemTitle }
type model struct {
list list.Model
form *huh.Form
state appState
choice int
output string
RemoteCommand string
PrivateKeyFile string
@ -71,8 +68,8 @@ func (m *model) ReadForm() {
func newModel() model {
items := []list.Item{
CommandItem{ItemTitle: "Run Command", ItemDescription: "Run a command on the remote host",},
CommandItem{ItemTitle: "Upload File", ItemDescription: "Upload a file to the remote host",},
CommandItem{ItemTitle: "Run Command", ItemDescription: "Run a command on the remote host"},
CommandItem{ItemTitle: "Upload File", ItemDescription: "Upload a file to the remote host"},
}
l := list.New(items, list.NewDefaultDelegate(), 80, 20)
l.Title = "Commands"
@ -83,6 +80,33 @@ func (m model) Init() tea.Cmd {
return nil
}
func execCommand(choice int, m model) tea.Cmd {
return func() tea.Msg {
var buf bytes.Buffer
var err error
if choice == 0 {
err = commands.RunCommand(commands.RunCommandArgs{
RemoteCommand: m.RemoteCommand,
PrivateKeyFile: m.PrivateKeyFile,
Username: m.Username,
Host: m.Host,
}, &buf, &buf)
} else {
err = commands.UploadFile(commands.UploadFileArgs{
LocalFilepath: m.LocalFilepath,
DestinationFilepath: m.DestinationFilepath,
PrivateKeyFile: m.PrivateKeyFile,
Username: m.Username,
Host: m.Host,
}, &buf, &buf)
}
if err != nil {
fmt.Fprintf(&buf, "\nError: %v", err)
}
return cmdResultMsg{output: buf.String()}
}
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch m.state {
case stateList:
@ -127,28 +151,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
if m.form.State == huh.StateCompleted {
m.ReadForm()
var execCmd tea.ExecCommand
if m.choice == 0 {
args := commands.RunCommandArgs{
RemoteCommand: m.RemoteCommand,
PrivateKeyFile: m.PrivateKeyFile,
Username: m.Username,
Host: m.Host,
}
execCmd = &sshExecCmd{run: func() { commands.RunCommand(args) }}
} else {
args := commands.UploadFileArgs{
LocalFilepath: m.LocalFilepath,
DestinationFilepath: m.DestinationFilepath,
PrivateKeyFile: m.PrivateKeyFile,
Username: m.Username,
Host: m.Host,
}
execCmd = &sshExecCmd{run: func() { commands.UploadFile(args) }}
}
m.state = stateList
choice := m.choice
m.form = nil
return m, tea.Exec(execCmd, func(err error) tea.Msg { return nil })
m.state = stateOutput
return m, execCommand(choice, m)
}
if m.form.State == huh.StateAborted {
m.state = stateList
@ -156,6 +162,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
}
return m, cmd
case stateOutput:
if _, ok := msg.(tea.KeyMsg); ok {
m.state = stateList
return m, nil
}
}
switch msg := msg.(type) {
case cmdResultMsg:
m.output = msg.output
m.state = stateOutput
}
return m, nil
@ -167,6 +185,8 @@ func (m model) View() string {
return m.list.View()
case stateForm:
return m.form.View()
case stateOutput:
return m.output + "\n\nPress any key to return..."
}
return ""
}

Loading…
Cancel
Save