add ability for live output, add events system
This commit is contained in:
parent
4e2c314c2c
commit
750686fd1f
3 changed files with 138 additions and 16 deletions
10
linux/events.go
Normal file
10
linux/events.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package linux
|
||||
|
||||
const (
|
||||
EventOutput = iota
|
||||
EventStderr
|
||||
)
|
||||
|
||||
type EventOutputData struct {
|
||||
Output string
|
||||
}
|
|
@ -2,10 +2,17 @@ package linux
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type LinuxCommand struct {
|
||||
Options CommandOptions
|
||||
handlers map[int]interface{}
|
||||
wg sync.WaitGroup
|
||||
stdout io.ReadCloser
|
||||
stderr io.ReadCloser
|
||||
stdin io.WriteCloser
|
||||
}
|
||||
|
||||
type CommandOptions struct {
|
||||
|
@ -24,4 +31,5 @@ var (
|
|||
ErrRunningCmd = errors.New("error running command")
|
||||
ErrCommandNotFound = errors.New("error command not found")
|
||||
ErrCommandNotExecutable = errors.New("error command not executable")
|
||||
ErrInvalidHandler = errors.New("invalid handler")
|
||||
)
|
||||
|
|
126
linux/run.go
126
linux/run.go
|
@ -1,10 +1,15 @@
|
|||
package linux
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"golang.org/x/sys/unix"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func NewCommand(options CommandOptions) (*LinuxCommand, error) {
|
||||
|
@ -23,33 +28,67 @@ func NewCommand(options CommandOptions) (*LinuxCommand, error) {
|
|||
|
||||
return &LinuxCommand{
|
||||
Options: options,
|
||||
handlers: make(map[int]interface{}),
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (cmd *LinuxCommand) AddHandler(handler interface{}) error {
|
||||
|
||||
switch h := handler.(type) {
|
||||
case func(data EventOutputData) error:
|
||||
cmd.handlers[EventOutput] = h
|
||||
break
|
||||
default:
|
||||
return ErrInvalidHandler
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (cmd *LinuxCommand) Run() error {
|
||||
|
||||
var sourceCommand string
|
||||
var sourceCommand strings.Builder
|
||||
for _, value := range cmd.Options.Sources {
|
||||
sourceCommand += fmt.Sprintf("source %s && ", value)
|
||||
sourceCommand.WriteString(fmt.Sprintf("source %s && ", value))
|
||||
}
|
||||
|
||||
command := exec.Command(cmd.Options.Shell, "-c", sourceCommand+cmd.Options.Command)
|
||||
var commandOptions strings.Builder
|
||||
commandOptions.WriteString(" ")
|
||||
for index, arg := range cmd.Options.Args {
|
||||
if len(cmd.Options.Args)-1 == index {
|
||||
commandOptions.WriteString(fmt.Sprintf("%s", arg))
|
||||
} else {
|
||||
commandOptions.WriteString(fmt.Sprintf("%s ", arg))
|
||||
}
|
||||
}
|
||||
|
||||
command := exec.Command(cmd.Options.Shell, "-c", sourceCommand.String()+cmd.Options.Command+commandOptions.String())
|
||||
command.Dir = cmd.Options.Cwd
|
||||
command.Args = append(command.Args, cmd.Options.Args...)
|
||||
|
||||
// Loop through env to format and add them to the command.
|
||||
for key, value := range cmd.Options.Env {
|
||||
command.Env = append(command.Env, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
var signalChannel chan os.Signal
|
||||
|
||||
isCommandExecutable, err := cmd.isCommandExecutable(cmd.Options.Command)
|
||||
if len(cmd.handlers) != 0 {
|
||||
command.SysProcAttr = &unix.SysProcAttr{Setsid: true}
|
||||
signalChannel = make(chan os.Signal, 1)
|
||||
signal.Notify(signalChannel, unix.SIGINT, unix.SIGTERM)
|
||||
|
||||
var err error
|
||||
cmd.stdout, err = command.StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isCommandExecutable {
|
||||
return ErrCommandNotExecutable
|
||||
cmd.stdin, err = command.StdinPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd.stderr, err = command.StderrPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := command.Start(); err != nil {
|
||||
|
@ -65,6 +104,67 @@ func (cmd *LinuxCommand) Run() error {
|
|||
}
|
||||
}
|
||||
|
||||
cmd.wg.Add(3)
|
||||
|
||||
go func() {
|
||||
defer cmd.wg.Done()
|
||||
scanner := bufio.NewScanner(cmd.stderr)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if h, ok := cmd.handlers[EventOutput]; ok {
|
||||
if err := h.(func(data EventOutputData) error)(EventOutputData{
|
||||
Output: line,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer cmd.wg.Done()
|
||||
|
||||
scanner := bufio.NewScanner(cmd.stdout)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if h, ok := cmd.handlers[EventOutput]; ok {
|
||||
if err := h.(func(data EventOutputData) error)(EventOutputData{
|
||||
Output: line,
|
||||
}); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer cmd.wg.Done()
|
||||
|
||||
select {
|
||||
case _, ok := <-signalChannel:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if err := unix.Kill(-command.Process.Pid, syscall.SIGINT); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Loop through env to format and add them to the command.
|
||||
for key, value := range cmd.Options.Env {
|
||||
command.Env = append(command.Env, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
|
||||
isCommandExecutable, err := cmd.isCommandExecutable(cmd.Options.Command)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isCommandExecutable {
|
||||
return ErrCommandNotExecutable
|
||||
}
|
||||
|
||||
if err := command.Wait(); err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) {
|
||||
|
@ -78,6 +178,10 @@ func (cmd *LinuxCommand) Run() error {
|
|||
}
|
||||
}
|
||||
|
||||
close(signalChannel)
|
||||
signal.Stop(signalChannel)
|
||||
cmd.wg.Wait()
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue