old_console/correlator/rules/action_bash.go
2024-11-02 14:12:45 +03:00

198 lines
3.9 KiB
Go

package rules
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"iwarma.ru/console/correlator/events"
"os"
"os/exec"
"regexp"
"strings"
"text/template"
"iwarma.ru/console/correlator/config"
log "github.com/sirupsen/logrus"
"github.com/spf13/viper"
)
const (
BashActionType = "bash"
)
type BashAction struct {
Body string `json:"body"`
bodyTemplate *template.Template
}
func (action *BashAction) GetType() string {
return BashActionType
}
func (action BashAction) ToInterface() (map[string]interface{}, error) {
result := make(map[string]interface{})
result["body"] = action.Body
result["type"] = BashActionType
return result, nil
}
func (action BashAction) MarshalJSON() ([]byte, error) {
data, err := action.ToInterface()
if err != nil {
return nil, err
}
return json.Marshal(data)
}
func (action *BashAction) UnmarshalJSON(b []byte) error {
cur := struct {
CurType string `json:"type"`
Body string `json:"body"`
}{}
err := json.Unmarshal(b, &cur)
if err != nil {
return err
}
if cur.CurType != BashActionType {
return fmt.Errorf("bad action type, Expect %v, got %v", BashActionType, cur.CurType)
}
action.Body = cur.Body
return nil
}
func (action *BashAction) ParseInterface(v interface{}) error {
m, ok := v.(map[string]interface{})
if !ok {
return fmt.Errorf("can't parse %v from %T", v, v)
}
body, ok := m["body"].(string)
if !ok {
return errors.New("no body")
}
t, ok := m["type"].(string)
if !ok {
return errors.New("no type")
}
if t != BashActionType {
return fmt.Errorf("bad type, expect %v got %v", BashActionType, t)
}
action.Body = body
return nil
}
// See https://stackoverflow.com/a/52594719
func removeLBR(text string) string {
re := regexp.MustCompile(`\x{000D}\x{000A}|[\x{000A}\x{000B}\x{000C}\x{000D}\x{0085}\x{2028}\x{2029}]`)
return re.ReplaceAllString(text, "\n")
}
func (action *BashAction) Perform(events *[]*events.Event) error {
cl := log.WithFields(log.Fields{"type": BashActionType, "event_count": len(*events)})
cl.Debug("Start action")
defer cl.Debug("End action")
var err error
// Check if we need to prepare template
if action.bodyTemplate == nil {
text := strings.Replace(action.Body, `\r\n`, "\n", -1)
text = removeLBR(text)
action.bodyTemplate, err = template.New("Body").Parse(text)
if err != nil {
cl.Errorf("Can't create body template: %v", err)
return err
}
}
// Actual action
for _, event := range *events {
// Render script body
body, err := renderTemplate(action.bodyTemplate, event)
if err != nil {
cl.Errorf("Can't render script body: %v", err)
return err
}
// Create temp file
file, err := ioutil.TempFile("", "action.*.sh")
if err != nil {
cl.Errorf("Can't create script file: %v", err)
return err
}
// Remove temp script
defer func() {
err = os.Remove(file.Name())
if err != nil {
cl.Errorf("Can't remove temp script: %v", err)
}
}()
count, err := file.Write([]byte(body))
if err != nil {
cl.Errorf("Can't write script body: %v", err)
return err
}
if count != len([]byte(body)) {
cl.Errorf("Write bad bytes count, expec %v, got %v", len([]byte(body)), count)
}
err = file.Close()
if err != nil {
cl.Errorf("Can't close temp script: %v", err)
}
err = os.Chmod(file.Name(), 0775)
if err != nil {
cl.Errorf("Can't set script permissions: %v", err)
return err
}
if viper.GetBool(config.DebugDumpRequest) {
info, err := os.Stat(file.Name())
if os.IsNotExist(err) {
cl.Errorf("Script %v does not exist #0", file.Name())
}
cl.Debugf("File mode: %v", info.Mode())
cl.Debugf("File size: %v", info.Size())
}
var buf bytes.Buffer
cmd := exec.Command(file.Name())
cmd.Stdout = &buf
cmd.Stderr = &buf
// TODO: Maybe set to goroutine
err = cmd.Run()
if err != nil {
cl.Errorf("Script execution error: %v", err)
return err
}
if viper.GetBool(config.Verbose) {
cl.Infof("Script output:\n%v", buf.String())
}
}
return nil
}