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 }