|
|
@@ -0,0 +1,383 @@
|
|
|
+package main
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "encoding/json"
|
|
|
+ "flag"
|
|
|
+ "fmt"
|
|
|
+ "io/ioutil"
|
|
|
+ "os"
|
|
|
+ "path"
|
|
|
+ "path/filepath"
|
|
|
+ "strings"
|
|
|
+ "text/template"
|
|
|
+ "unicode/utf8"
|
|
|
+
|
|
|
+ "git.wecise.com/wecise/odb-go/odb"
|
|
|
+ "gitee.com/wecisecode/util/logger"
|
|
|
+)
|
|
|
+
|
|
|
+// https://stackoverflow.com/questions/22367337/last-item-in-a-template-range
|
|
|
+var ErrBadPattern = path.ErrBadPattern
|
|
|
+
|
|
|
+type RuleStru struct {
|
|
|
+ Class string
|
|
|
+ Bucket string
|
|
|
+ VarName string
|
|
|
+
|
|
|
+ Param []string
|
|
|
+
|
|
|
+ NickName string
|
|
|
+ PrintID string
|
|
|
+ PrintAtTime string
|
|
|
+ SnmpResult []string // snmpResult0, ...
|
|
|
+ TmplIndex []string // {{index .value 0}} , ...
|
|
|
+}
|
|
|
+
|
|
|
+//-------------------------------------------------------------------------------------------------
|
|
|
+// bucket2rule -class "class1, class2" -tmpl "myrule.tmpl" -type "etcd"/"file" -out "/opt/syslog"
|
|
|
+// ./bucket2rule -keyspace "matrix" -hosts "172.26.38.247" -class "/matrix/entity/linux" -except "*_baseline*"
|
|
|
+// ./bucket2rule -keyspace "matrix" -hosts "172.26.38.247" -class "/matrix/entity/linux" -except "*_baseline*" -type "file" -out "/opt/matrix/agent/bin/output"
|
|
|
+//-------------------------------------------------------------------------------------------------
|
|
|
+
|
|
|
+func main() {
|
|
|
+
|
|
|
+ class := flag.String("class", "", "class list, sep with ',' .")
|
|
|
+ myhost := flag.String("hosts", "", "no defualt ")
|
|
|
+
|
|
|
+ tmpl := flag.String("tmpl", "", "template file, defualt rule.tmpl in current dir")
|
|
|
+ out_type := flag.String("type", "", "output file type, defualt json for etcd")
|
|
|
+ out := flag.String("out", "", "output file dir if type=file or etcd path if type=json, default /$keyspace/rules ")
|
|
|
+ varname := flag.String("varname", "", "default _DATA ")
|
|
|
+ keyspace := flag.String("keyspace", "", "default matrix ")
|
|
|
+ bucket := flag.String("bucket", "", "bucket field list, , sep with ',' default all fields")
|
|
|
+ except := flag.String("except", "", "except bucket field list, sep with ',' default none .")
|
|
|
+
|
|
|
+ flag.Parse()
|
|
|
+
|
|
|
+ var clazz, buckets, excepts []string
|
|
|
+ if *class == "" {
|
|
|
+ logger.Errorf("class can't be empty .")
|
|
|
+ os.Exit(1)
|
|
|
+ } else {
|
|
|
+ clazz = strings.Split(*class, ",")
|
|
|
+ }
|
|
|
+
|
|
|
+ if *bucket != "" {
|
|
|
+ buckets = strings.Split(*bucket, ",")
|
|
|
+ }
|
|
|
+
|
|
|
+ if *except != "" {
|
|
|
+ excepts = strings.Split(*except, ",")
|
|
|
+ }
|
|
|
+
|
|
|
+ var hosts []string
|
|
|
+ if *myhost == "" {
|
|
|
+ logger.Errorf("hosts can't be empty .")
|
|
|
+ os.Exit(1)
|
|
|
+ } else {
|
|
|
+ hosts = strings.Split(*myhost, ",")
|
|
|
+ }
|
|
|
+
|
|
|
+ if *keyspace == "" {
|
|
|
+ *keyspace = "matrix"
|
|
|
+ }
|
|
|
+
|
|
|
+ if *tmpl == "" {
|
|
|
+ *tmpl = "rule.tmpl"
|
|
|
+ }
|
|
|
+
|
|
|
+ if *out_type == "" {
|
|
|
+ *out_type = "json"
|
|
|
+ }
|
|
|
+
|
|
|
+ if *out == "" {
|
|
|
+ *out = fmt.Sprintf("/%s/rules", *keyspace)
|
|
|
+ }
|
|
|
+
|
|
|
+ if *varname == "" {
|
|
|
+ *varname = "_DATA"
|
|
|
+ }
|
|
|
+
|
|
|
+ handle, err := template.ParseFiles(*tmpl)
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ logger.Error(err)
|
|
|
+ os.Exit(1)
|
|
|
+ }
|
|
|
+
|
|
|
+ strus := ruleData(*keyspace, hosts, clazz, buckets, excepts)
|
|
|
+
|
|
|
+ result := map[string]string{}
|
|
|
+ for _, stru := range strus {
|
|
|
+ stru.PrintID = `{{.id}}`
|
|
|
+ stru.PrintAtTime = `{{.attime}}`
|
|
|
+ stru.SnmpResult = make([]string, len(stru.Param))
|
|
|
+ stru.TmplIndex = make([]string, len(stru.Param))
|
|
|
+ stru.VarName = *varname
|
|
|
+
|
|
|
+ for i := range stru.Param {
|
|
|
+ stru.SnmpResult[i] = fmt.Sprintf("snmpResult%d", i)
|
|
|
+ stru.TmplIndex[i] = fmt.Sprintf(`{{index .value %d}}`, i)
|
|
|
+ }
|
|
|
+
|
|
|
+ buf := &bytes.Buffer{}
|
|
|
+ if err := handle.ExecuteTemplate(buf, *tmpl, stru); err != nil {
|
|
|
+ logger.Error(err)
|
|
|
+ os.Exit(1)
|
|
|
+ } else {
|
|
|
+ switch *out_type {
|
|
|
+ case "file":
|
|
|
+ key := fmt.Sprintf(`%s_%s.rule`, stru.Bucket, stru.NickName)
|
|
|
+ result[key] = buf.String()
|
|
|
+ case "json":
|
|
|
+ key := fmt.Sprintf(`%s/%s_%s.rule`, *out, stru.Bucket, stru.NickName)
|
|
|
+ result[key] = buf.String()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ switch *out_type {
|
|
|
+ case "file":
|
|
|
+ for k, v := range result {
|
|
|
+ file := filepath.Join(*out, k)
|
|
|
+ if err = ioutil.WriteFile(file, []byte(v), 0666); err != nil {
|
|
|
+ logger.Error(err)
|
|
|
+ os.Exit(1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ case "json":
|
|
|
+ if bs, err := json.Marshal(result); err != nil {
|
|
|
+ logger.Error(err)
|
|
|
+ } else {
|
|
|
+ if err = ioutil.WriteFile("output.json", bs, 0666); err != nil {
|
|
|
+ logger.Error(err)
|
|
|
+ os.Exit(1)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func ruleData(keyspace string, hosts, clazz, buckets, excepts []string) []*RuleStru {
|
|
|
+
|
|
|
+ rules := []*RuleStru{}
|
|
|
+ client, err := odb.NewClient(&odb.Config{
|
|
|
+ Keyspace: keyspace,
|
|
|
+ Hosts: hosts,
|
|
|
+ })
|
|
|
+
|
|
|
+ if err != nil {
|
|
|
+ logger.Error(err)
|
|
|
+ return rules
|
|
|
+ }
|
|
|
+ defer func() {
|
|
|
+ if err := client.Close(); err != nil {
|
|
|
+ logger.Error(err)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ for _, class := range clazz {
|
|
|
+
|
|
|
+ class = strings.TrimSpace(class)
|
|
|
+
|
|
|
+ var nickname string
|
|
|
+ if res, err := client.Query(fmt.Sprintf("select nickname from /system/class where name='%s'", class)).WithMeta(odb.QueryMeta{"meta": "false"}).Do(); err != nil {
|
|
|
+ logger.Error(err)
|
|
|
+ } else if len(res.Data) > 0 {
|
|
|
+ nickname = res.Data[0]["nickname"].(string)
|
|
|
+ if res, err := client.Query(fmt.Sprintf("select field, param from /system/tsdb where class='%s'", class)).WithMeta(odb.QueryMeta{"meta": "false"}).Do(); err != nil {
|
|
|
+ logger.Error(err)
|
|
|
+ } else {
|
|
|
+ for _, row := range res.Data {
|
|
|
+ field := row["field"].(string)
|
|
|
+ if buckets != nil {
|
|
|
+ find := false
|
|
|
+ for _, bucket := range buckets {
|
|
|
+ bucket = strings.TrimSpace(bucket)
|
|
|
+ if bucket == field {
|
|
|
+ find = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !find {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if excepts != nil {
|
|
|
+ find := false
|
|
|
+ for _, except := range excepts {
|
|
|
+ except = strings.TrimSpace(except)
|
|
|
+ if strings.ContainsAny(except, "*?") {
|
|
|
+ if match, _ := ExpMatch(except, field); match {
|
|
|
+ find = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if except == field {
|
|
|
+ find = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if find {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ param := []string{}
|
|
|
+ if v := row["param"]; v != nil {
|
|
|
+ for _, val := range v.([]interface{}) {
|
|
|
+ param = append(param, val.(string))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ rules = append(rules, &RuleStru{Class: class, NickName: nickname, Bucket: field, Param: param})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return rules
|
|
|
+}
|
|
|
+
|
|
|
+func ExpMatch(pattern, name string) (bool, error) {
|
|
|
+ // check some base cases
|
|
|
+ patternLen, nameLen := len(pattern), len(name)
|
|
|
+ if patternLen == 0 && nameLen == 0 {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ if patternLen == 0 {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+ if nameLen == 0 && pattern != "*" {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // check for matches one rune at a time
|
|
|
+ patIdx, nameIdx := 0, 0
|
|
|
+ for patIdx < patternLen && nameIdx < nameLen {
|
|
|
+ patRune, patAdj := utf8.DecodeRuneInString(pattern[patIdx:])
|
|
|
+ nameRune, nameAdj := utf8.DecodeRuneInString(name[nameIdx:])
|
|
|
+ if patRune == '\\' {
|
|
|
+ // handle escaped runes
|
|
|
+ patIdx += patAdj
|
|
|
+ patRune, patAdj = utf8.DecodeRuneInString(pattern[patIdx:])
|
|
|
+ if patRune == utf8.RuneError {
|
|
|
+ return false, ErrBadPattern
|
|
|
+ } else if patRune == nameRune {
|
|
|
+ patIdx += patAdj
|
|
|
+ nameIdx += nameAdj
|
|
|
+ } else {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+ } else if patRune == '*' {
|
|
|
+ // handle stars
|
|
|
+ if patIdx += patAdj; patIdx >= patternLen {
|
|
|
+ // a star at the end of a pattern will always
|
|
|
+ // match the rest of the path
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ // check if we can make any matches
|
|
|
+ for ; nameIdx < nameLen; nameIdx += nameAdj {
|
|
|
+ if m, _ := ExpMatch(pattern[patIdx:], name[nameIdx:]); m {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false, nil
|
|
|
+ } else if patRune == '[' {
|
|
|
+ // handle character sets
|
|
|
+ patIdx += patAdj
|
|
|
+ endClass := indexRuneWithEscaping(pattern[patIdx:], ']')
|
|
|
+ if endClass == -1 {
|
|
|
+ return false, ErrBadPattern
|
|
|
+ }
|
|
|
+ endClass += patIdx
|
|
|
+ classRunes := []rune(pattern[patIdx:endClass])
|
|
|
+ classRunesLen := len(classRunes)
|
|
|
+ if classRunesLen > 0 {
|
|
|
+ classIdx := 0
|
|
|
+ matchClass := false
|
|
|
+ if classRunes[0] == '^' {
|
|
|
+ classIdx++
|
|
|
+ }
|
|
|
+ for classIdx < classRunesLen {
|
|
|
+ low := classRunes[classIdx]
|
|
|
+ if low == '-' {
|
|
|
+ return false, ErrBadPattern
|
|
|
+ }
|
|
|
+ classIdx++
|
|
|
+ if low == '\\' {
|
|
|
+ if classIdx < classRunesLen {
|
|
|
+ low = classRunes[classIdx]
|
|
|
+ classIdx++
|
|
|
+ } else {
|
|
|
+ return false, ErrBadPattern
|
|
|
+ }
|
|
|
+ }
|
|
|
+ high := low
|
|
|
+ if classIdx < classRunesLen && classRunes[classIdx] == '-' {
|
|
|
+ // we have a range of runes
|
|
|
+ if classIdx++; classIdx >= classRunesLen {
|
|
|
+ return false, ErrBadPattern
|
|
|
+ }
|
|
|
+ high = classRunes[classIdx]
|
|
|
+ if high == '-' {
|
|
|
+ return false, ErrBadPattern
|
|
|
+ }
|
|
|
+ classIdx++
|
|
|
+ if high == '\\' {
|
|
|
+ if classIdx < classRunesLen {
|
|
|
+ high = classRunes[classIdx]
|
|
|
+ classIdx++
|
|
|
+ } else {
|
|
|
+ return false, ErrBadPattern
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if low <= nameRune && nameRune <= high {
|
|
|
+ matchClass = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if matchClass == (classRunes[0] == '^') {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return false, ErrBadPattern
|
|
|
+ }
|
|
|
+ patIdx = endClass + 1
|
|
|
+ nameIdx += nameAdj
|
|
|
+ } else if patRune == '?' || patRune == nameRune {
|
|
|
+ // handle single-rune wildcard
|
|
|
+ patIdx += patAdj
|
|
|
+ nameIdx += nameAdj
|
|
|
+ } else {
|
|
|
+ return false, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if patIdx >= patternLen && nameIdx >= nameLen {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ if nameIdx >= nameLen && pattern[patIdx:] == "*" || pattern[patIdx:] == "**" {
|
|
|
+ return true, nil
|
|
|
+ }
|
|
|
+ return false, nil
|
|
|
+}
|
|
|
+
|
|
|
+// Find the first index of a rune in a string,
|
|
|
+// ignoring any times the rune is escaped using "\".
|
|
|
+func indexRuneWithEscaping(s string, r rune) int {
|
|
|
+ end := strings.IndexRune(s, r)
|
|
|
+ if end == -1 {
|
|
|
+ return -1
|
|
|
+ }
|
|
|
+ if end > 0 && s[end-1] == '\\' {
|
|
|
+ start := end + utf8.RuneLen(r)
|
|
|
+ end = indexRuneWithEscaping(s[start:], r)
|
|
|
+ if end != -1 {
|
|
|
+ end += start
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return end
|
|
|
+}
|