run commands inside TUI and return to list

master
Joshua Herring 1 month ago
parent cb1ce4dab9
commit 3a9175e5d7

@ -2,6 +2,7 @@ package commands
import ( import (
"fmt" "fmt"
"io"
"os" "os"
) )
@ -11,6 +12,7 @@ type RunCommandArgs struct {
PrivateKeyFile string `json:"private_key_file"` PrivateKeyFile string `json:"private_key_file"`
Username string `json:"username"` Username string `json:"username"`
Host string `json:"host"` Host string `json:"host"`
Sudo bool `json:"sudo"`
} }
func (rca *RunCommandArgs) ReadArgs() { 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{} client, err := GetClient(args.PrivateKeyFile, args.Username, args.Host)
//args.ReadArgs() if err != nil {
return err
client := GetClient(args.PrivateKeyFile, args.Username, args.Host) }
defer client.Close() defer client.Close()
session, err := client.NewSession() session, err := client.NewSession()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error creating SSH session: %v\n", err) return fmt.Errorf("error creating SSH session: %w", err)
os.Exit(1)
} }
defer session.Close() defer session.Close()
session.Stdout = os.Stdout session.Stdout = stdout
session.Stderr = os.Stderr session.Stderr = stderr
if err := session.Run(args.RemoteCommand); err != nil { if err := session.Run(args.RemoteCommand); err != nil {
fmt.Fprintf(os.Stderr, "Error running command: %v\n", err) return fmt.Errorf("error running command: %w", err)
os.Exit(1)
} }
return return nil
} }

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"io" "io"
"os" "os"
"path/filepath" "path/filepath"
) )
@ -33,58 +32,52 @@ func (ufa *UploadFileArgs) ReadArgs() {
} }
func UploadFile(args UploadFileArgs) { func UploadFile(args UploadFileArgs, stdout, stderr io.Writer) error {
//args := UploadFileArgs{}
//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() defer client.Close()
session, err := client.NewSession() session, err := client.NewSession()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error creating SSH session: %v\n", err) return fmt.Errorf("error creating SSH session: %w", err)
os.Exit(1)
} }
defer session.Close() defer session.Close()
source_file, err := os.Open(args.LocalFilepath) source_file, err := os.Open(args.LocalFilepath)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error opening local file: %v\n", err) return fmt.Errorf("error opening local file: %w", err)
os.Exit(1)
} }
defer source_file.Close() defer source_file.Close()
info, err := source_file.Stat() info, err := source_file.Stat()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error stating local file: %v\n", err) return fmt.Errorf("error stating local file: %w", err)
os.Exit(1)
} }
w, err := session.StdinPipe() w, err := session.StdinPipe()
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error creating stdin pipe: %v\n", err) return fmt.Errorf("error creating stdin pipe: %w", err)
os.Exit(1)
} }
if err := session.Start("scp -t " + args.DestinationFilepath); err != nil { if err := session.Start("scp -t " + args.DestinationFilepath); err != nil {
fmt.Fprintf(os.Stderr, "Error starting scp: %v\n", err) return fmt.Errorf("error starting scp: %w", err)
os.Exit(1)
} }
fmt.Fprintf(w, "C0644 %d %s\n", info.Size(), filepath.Base(args.LocalFilepath)) fmt.Fprintf(w, "C0644 %d %s\n", info.Size(), filepath.Base(args.LocalFilepath))
if _, err := io.Copy(w, source_file); err != nil { if _, err := io.Copy(w, source_file); err != nil {
fmt.Fprintf(os.Stderr, "Error sending file: %v\n", err) return fmt.Errorf("error sending file: %w", err)
os.Exit(1)
} }
fmt.Fprint(w, "\x00") fmt.Fprint(w, "\x00")
w.Close() w.Close()
if err := session.Wait(); err != nil { if err := session.Wait(); err != nil {
fmt.Fprintf(os.Stderr, "Error during scp transfer: %v\n", err) return fmt.Errorf("error during scp transfer: %w", err)
os.Exit(1)
} }
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) key_bytes, err := os.ReadFile(private_key_file)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error reading private key from file %s: %v\n", private_key_file, err) return nil, fmt.Errorf("error reading private key from file %s: %w", private_key_file, err)
os.Exit(1)
} }
signer, err := ssh.ParsePrivateKey(key_bytes) signer, err := ssh.ParsePrivateKey(key_bytes)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing private key: %v\n", err) return nil, fmt.Errorf("error parsing private key: %w", err)
os.Exit(1)
} }
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{ config := &ssh.ClientConfig{
User: username, User: username,
@ -63,10 +64,8 @@ func GetClient(private_key_file, username, host string) *ssh.Client {
client, err := ssh.Dial("tcp", addr, config) client, err := ssh.Dial("tcp", addr, config)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error connecting to server: %v\n", err) return nil, fmt.Errorf("error connecting to server: %w", err)
os.Exit(1)
} }
return client return client, nil
} }

@ -1,8 +1,8 @@
package main package main
import ( import (
"bytes"
"fmt" "fmt"
"io"
"os" "os"
"github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/list"
@ -11,22 +11,18 @@ import (
"periodic/prddeploy/commands" "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 type appState int
const ( const (
stateList appState = iota stateList appState = iota
stateForm stateForm
stateOutput
) )
type cmdResultMsg struct {
output string
}
type CommandItem struct { type CommandItem struct {
ItemTitle string `json:"title"` ItemTitle string `json:"title"`
ItemDescription string `json:"description"` ItemDescription string `json:"description"`
@ -42,6 +38,7 @@ type model struct {
form *huh.Form form *huh.Form
state appState state appState
choice int choice int
output string
RemoteCommand string RemoteCommand string
PrivateKeyFile string PrivateKeyFile string
@ -71,8 +68,8 @@ func (m *model) ReadForm() {
func newModel() model { func newModel() model {
items := []list.Item{ items := []list.Item{
CommandItem{ItemTitle: "Run Command", ItemDescription: "Run a command on 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",}, CommandItem{ItemTitle: "Upload File", ItemDescription: "Upload a file to the remote host"},
} }
l := list.New(items, list.NewDefaultDelegate(), 80, 20) l := list.New(items, list.NewDefaultDelegate(), 80, 20)
l.Title = "Commands" l.Title = "Commands"
@ -83,6 +80,33 @@ func (m model) Init() tea.Cmd {
return nil 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) { func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch m.state { switch m.state {
case stateList: case stateList:
@ -127,28 +151,10 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
if m.form.State == huh.StateCompleted { if m.form.State == huh.StateCompleted {
m.ReadForm() m.ReadForm()
var execCmd tea.ExecCommand choice := m.choice
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
m.form = nil 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 { if m.form.State == huh.StateAborted {
m.state = stateList m.state = stateList
@ -156,6 +162,18 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
return m, cmd 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 return m, nil
@ -167,6 +185,8 @@ func (m model) View() string {
return m.list.View() return m.list.View()
case stateForm: case stateForm:
return m.form.View() return m.form.View()
case stateOutput:
return m.output + "\n\nPress any key to return..."
} }
return "" return ""
} }

Loading…
Cancel
Save